文章目录
前言
指针是c/c++的重要概念,必须掌握!要搞清楚指针就需要搞清楚地址,地址范围很广:指针(它的范围很广,可以是普通变量的地址,可以是数组元素的地址(包括数组数组和字符数组),可以是函数的入口地址)、数组名(数组的第一个元素的地址,不能进行++指向下一个元素,类似于是一个const常量,或者是一个指针常量)、函数名(函数的入口地址),这才是使用指针的原因,太灵活了!
指针是地址,指针变量是存储地址的变量,①可以是变量的地址(数组元素是其中的特列),②也可以是函数的的地址,分别称为指向变量的指针变量,指向函的指针变量;③多维数组中某一行整体的地址;多维数组中整体的地址;⑤指针变量的地址(指针的指针)
下面将对①和②分别做说明,③和④参考8.多维数组和指针,⑤参考7.指针数组
一、指针
1.普通变量的指针是什么
1)程序经过编译之后已经将变量名转换为变量的地址,对变量值得存取都是通过地址进行的 2)直接存取和间接存取 3)指针:一个变量的地址称为该变量的指针,所以变量的指针就是变量的地址; 指针变量:一个存储指针(地址)的变量 ***4)两个重要的运算符:&--取地址运算符 和 * 指针运算符(指向的作用)***2.指向普通变量的指针变量
1)定义:基类型 * 指针变量名
a)指针变量的基类型就是该指针指向的变量的类型;
b)定义指针变量的时候必须指定基类型,如果想指针引用一个变量,只知道地址(如0x080001000)是不行的,因为系统不知道是从这个地址取几个数据,所以一个变量的指针必须包含两个含义:变量的地址以及变量的类型。**指针变量是基本数据类型派生出来的数据类型,它不能离开基本类型而存在。**指针的类型就是(基类型 *),或者叫指向基类型的指针。
c)不能只用一个整数赋值给指针变量:int *pt = 200;可能你的想法是200是一个地址值,但是编译器不知道,会将200作为整数常量,因此会报错,所以赋值给指针变量必须是明确的编译器认为的地址,如&取地址符、数组名或者其他(这个地方需要再确认);
d)指针变量是在函数开始就定义的,此时已经确认了指针的类型,所以不能再修改,赋值给他的数据必须是相同基类型的变量取地址符,否则编译器会报错,编译器会认为你一开始就不知你要操作的数是什么类型,后面的操作就更不知道要乱搞什么,所以编译都不让你过!;
e)指针变量一定要初始化或者赋合适的地址值,不然编译过了,但是使用的时候会报错。
2)直接存取和间接存取
3.指向普通变量的指针变量的使用场合
1)指针作为函数参数:函数的参数可以是整型、浮点型、字符型、数组、数组元素(整型、浮点型、字符型),还可以是指针,它的作用是将一个变量的地址传给被调用函数的形参。
// lesson_07.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include<iostream>
using namespace std;
int main(int argc, char* argv[])
{
//函数原型声明,这样写更能体现函数的形参是一个指向整型的指针,
//更建议大家这么写,形参的类型一目了然,类似于void swap(int [], int []),一看就知道形参是int型的数组
void swap(int *, int *);
//定义性声明两个int变量
int a, b;
//定义性声明两个指向int的指针变量,并初始化,注意指针变量一定要有确定的值再使用,
//否则无确定值操作对系统是很危险的,要么定义的时候初始化,要么在操作前赋值
int *pt1 = &a, *pt2 = &b;
cout << "please input two numbers:" << endl;
cin >> a >> b;
swap(pt1, pt2);
cout << "max =" << *pt2 <<", min = " << *pt1 <<endl;
return 0;
}
void swap(int *p1, int *p2)
{
int temp;
if(*p1 > *p2)
{
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
}
2)指针和数组
通过前言,我们知道,数组名(如a[10])也是地址,只能指向第一个数组元素,类似于const常量,如果想进行a++;不行的,但是,int *b = a + 10;或者是int c = *(a + 5);类似于常量参与运算,是可以的,但是指针更灵活,关于数组与指针,讲以下3点:
int main(int argc, char* argv[])
{
int a[] = {1,2,3,4};
int *pt1 = a;//将数组首地址赋值给指针变量(地址变量)ptr, a是const常量
for(int i = 0; i < 4; i++)
{
// cout << *pt1 <<endl;//地址指向的内容
cout << *(a + i) << endl;
// pt1++; //指针变量自加
pt1 = a + i;
}
return 0;
}
a)程序中如有一个int a[5]的数组,引用其中a[3]这个元素,是怎么做的呢????实际上,编译器在编译的时候,是将这样处理的,首先找数组首地址a,之后a + 3找到a[3]的地址,最后使用指向运算符*(a + 3 * (int型数据的长度 2))找到a[3]这个数据,编译器将数组名作为一个指针常量;
//下标法
int main(int argc, char* argv[])
{
int a[] = {1,2,3,4};
for(int i = 0; i < 4; i++)
{
cout << a[i] << endl;
}
return 0;
}
//数组名指针常量法
int main(int argc, char* argv[])
{
int a[] = {1,2,3,4};
for(int i = 0; i < 4; i++)
{
cout << *(a + i) << endl;
}
return 0;
}
//将数组名赋值给一个指针变量
int main(int argc, char* argv[])
{
int a[] = {1,2,3,4};
int *pt1 = a;//将数组首地址赋值给指针变量(地址变量)ptr, a是const常量
for(; pt1 < a + 4; pt1++)
{
cout << *pt1 <<endl;//地址指向的内容
}
return 0;
}
//三种方法中,最后一种效率最高,只有++和*两种操作符,速度最快,前两种都需要
// *(a + i * d(指的是数据类型长度))这一系列的操作,操作符较多,效率低
b)注意指针变量p指向的范围,如前面2.d)所说的,开始定义的时候说好的指向某种类型,但是没有说指针操作的范围,这是很危险的,需要程序员自身控制!!!!!!!!
c)通过指针变量来接受数组的首地址
int main(int argc, char* argv[])
{
void swap(int *);
int a[] = {1,2,3,4};
swap(a);
return 0;
}
void swap(int *p)
{
cout << *(p + 1) << endl;
}
3)指针和字符串
通过前面的课程,我们知道,字符字符串的操作可以通过以下方式:
a)C-string:字符数组,这个地方要注意,char a[ ] = "I love China!"的执行过程,双引号做了三个工作,首先申请了空间(在常量区),存放了字符串,在字符串尾加上了“\0”,将内容复制到了a[ ]所定义的空间中,所以有两个地方是 "I love China!\0“,这个和字面常量还不一样,如int b = 5;字面常量不申请了空间(在常量区),直接将5内容填充到b中。
b)C++:字符串对象
c)指针法:char *ptr = "I love China!"的执行过程,双引号做了三个工作,首先申请了空间(在常量区),存放了字符串,在字符串尾加上了“\0”,返回地址,这里所返回的地址就赋值给了char *类型的指针变量ptr。
//将字符串str1复制到字符串str2中
int main(int argc, char* argv[])
{
//声明str1字符数组,首先申请了空间(在常量区),存放了字符串,在字符串尾加上了“\0”,
//将内容复制到了str1[]所定义的空间中,所以有两个地方是 "I love China!\0“
//将数组名(第一个元素的地址)赋值给指针变量
char str1[] = "I love china!",str2[20], *p1 = str1, *p2 =str2;
//比较指针变量指向的内容是否是字符床结束符'\0'
for(;*p1 != '\0'; p1++, p2++)
{
*p2 = *p1;
}
//最后在str2最后加上'\0'
*p2 = '\0';
cout << "str1 = " << str1 << endl;
cout << "str2 = " << str2 << endl;
return 0;
}
4.函数的指针是什么
一个函数在编译的时候被分配一个入口地址,这个地址就是函数的指针(或者叫函数的入口地址)。
5.指向函数的指针的指针变量
1)定义:函数类型 ( 函数指针变量名)(函数参数表)*
2)与普通变量的指针变量依托于基类型一样,函数指针变量名依托于函数的类型;
3)指针变量的类型:和普通变量的指针变量一样,指针是地址,没有类型,只有指向的类型,所以准确的说法是,指向函数类型的指针变量。
//指向函数的指针变量
int main(int argc, char* argv[])
{
//函数原型声明
void putout(int);
//普通变量定义并初始化
int a = 5;
//函数指针定义不能直接初始化void (*po)(int a) = pout;这是不对的
void (*po)(int a);
//将函数名(函数的入口地址)赋值给指向函数的指针变量
po = putout;
//通过指针调用函数
po(a);
return 0;
}
void putout(int a)
{
cout << a << endl;
}
6.指向函数的指针变量的使用场合
1)函数指针作为函数形参接收函数的入口地址,这样调用不同的函数;
//指向函数的指针变量做为形参
int main(int argc, char* argv[])
{
//函数原型声明,有两个参数,一个是int型,一个是指向函数的函数指针
//注意这里如果这样声明void putout(int a, void (*p)(int));则在上面个的基础上
//定义了两个局部变量a,p,但是两者的作用于也仅仅在这里
void putout(int , void (*)(int));
void putout1(int);
void putout2(int);
//普通变量定义并初始化
int a = 5;
putout(a, putout1);
putout(a, putout2);
return 0;
}
//函数定义,有两个局部变量,一个是int型a,一个是指向函数的函数指针p
void putout(int a, void (*p)(int))
{
p(a);
}
void putout1(int a)
{
cout << a << endl;
}
void putout2(int a)
{
cout << (float)a / 10 << endl;
}
2)函数的返回值(函数类型)可以是整形、实型、字符型,当然也可以是指针类型(即返回地址):定义:函数类型 * 函数名(函数参数表),注意与指向函数的指向变量的区别。
7.指针数组和指向指针的指针
1)指针数组:类型名 * 数组名[数组长度 ]
a)注意和**类型名( * 指针名)[数组长度 ]**的区别
//指针数组
int main(int argc, char* argv[])
{
void sort(char *[], int);
//定义一个指针数组,初始化的时候"hello"等都会有地址,并将地址放在p1数组中
char *p1[3] = {"hello","c/c++","learn you!"};
sort(p1, 3);
cout << *p1 << endl;
cout << p1[1] << endl;
cout << p1[2] << endl;
return 0;
}
void sort(char *p[], int n)
{
char *temp;
for(int i = 0; i < 3; i++)
{
for(int j = i + 1; j < 3; j++)
{
if(strcmp(p[i], p[j]) > 0)
{
//将指针数组中的值换一下,这样指向就不一样了,
//但是字符串在字符串在内存中的地址和内容不变
temp = p[i];
p[i] = p[j];
p[j] = temp;
}
}
}
}
2)指向指针的指针
8.多维数组和指针
9.指针其他注意事项
①指针定义的时候与const结合
a)const 类型名 * 指针变量名 或者 类型名 const * 指针变量名:限制通过指针变量修改指向对象的值,对象的值能不能该,看对象自己是怎么定义的,我只能管到指针不要乱搞;
b)类型名 * const 指针变量名:指针变量的值是确定的,不能修改指向对象;
a)const 类型名 * const指针变量名:以上两着的结合
//const指针指针
int main(int argc, char* argv[])
{
int a = 10, b = 20;
const int *p1 = &a;
//int const*p1 = &a;//和上面是一样的
// *p1 = 50;//试图通过p1修改a的内容,不允许
a = 50; //这是允许的
//如果想a也不能改变,就定义成常变量const int a = 10;
int * const p2 = &a;
// p2 = &b;//试图通过p2内容,不允许
const int *const p3 = &a;
// *p3 = 50;//试图通过p3修改a的内容,不允许
// p3 = &b;//试图通过p3内容,不允许
return 0;
}
②void指针类型,注意与NULL的区别
a)定义:void *指针变量,表示指针变量的指向的基类型不确定,或者称为基类型为void,所以不能是用此指针变量进行操作,因为编译器也不知这么操作,他仅仅只能存储一个地址值;
b)这种类型的指针是一个过渡态,不能使用的,必须转换成具体的指向类型才能使用,编译器要求的,列如malloc函数开辟了一段空间,函数返回的就是空间的首地址,至于你要在里面存上面类型的数据,他是不知道的,所以返回的就是void *型的指针;
c)可以将非void的指针赋值给void的指针变量,但是void的指针赋值给非void的指针变量时必须强制转化。
//void *和非void *
int main(int argc, char* argv[])
{
float c = 3.14;//truncation from 'const double' to 'float'
//float c = 3.14f;//不加f默认时double
float *p1 = &c;
void *p2;
// p1 = p2;//error C2440: '=' : cannot convert from 'void *' to 'float *'
p1 = (int *)p2;
p2 = p1;
return 0;
}
d)int *p = NULL;//表示指向地址为0的整型数据
10.指针小结
①变量定义格式和变量的类型:这是一个有意思的东西,去掉变量名就是变量的类型
定义格式 | 类型 | 类型 |
---|---|---|
int a | int | 定义整形变量a |
int f() | int () | 定义返回值为int 的函数 |
int *p | int * | |
int *p[4] | int *[4] | 指针数组 |
int (*p)[4] | int (*)[4] | 定义一个指向包含4个元素的一维数组的指针变量 |
void *p | void | |
int *p[4][5] | int *[4][5] | 自己去领悟吧 |
int (*p)[4][5] | int (*)[4][5] | 自己去领悟吧 |
void (*p)() | int (*)() | 函数指针 |
… | … |
②变量的运算
变量指针的加减一个整数:是这么运算的;
指针变量的赋值:
两个指针变量相减;
两个指针变量的比较;
指针变量的强制赋值。
int main(int argc, char* argv[])
{
float c = 3.14f;
int a = 3;
float *p1 = &c;
int *p2 = &a;
int *p3;
int *p4 = &a;
// p3 = p2 - p1;//error C2230: '-' : indirection to different types
// p2 = p1;//error C2440: '=' : cannot convert from 'float *' to 'int *'
//这一点需要注意,p2 -p4,编译器认为结果是int型不是int *型,所以需要强制转换
//但是直接给他有没有问题p2 = p3;
// p3 = p2 - p4;//error C2440: '=' : cannot convert from 'int' to 'int *'
p3 = (int *)(p2 - p4);
p2 = p3;
return 0;
}
//指针的强制转化
int main(int argc, char* argv[])
{
void max(int, int);
void (*p3)(int, int) = max;
float c = 3.14;
float *p1 = &c;
int *p2 = (int *)p1;
p2 = (int *)p3;//函数指针都可以强制转换,太可怕了
return 0;
}
void max(int a, int b)
{
}
二、引用
1.什么是引用(C没有,C++有)
①作为动词:使用或者调用
②作为名词:为变量起个别名,共同占用一段地址
2.引用的入门
①声明:int &b = a;//是声明,不是定义,**在数据类型后面出现&是引用声明符,其他时候就是地址符;
②注意事项:
a)引用不是一种独立的数据类型,所以只有声明,没有定义;
b)一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象;
c)引用必须在创建时被初始化:①引用作为函数参数时,在函数调用的时候虚实结合实现的,即作为形参的引用时实参的别名;②其他时候在创建的时候初始化int &b = a;。
3.引用主要作用
C++增加引用机制,主要是把它作为函数参数,以扩充函数传递数据的功能。
函数的形参和实参直接传递方式有以下几种:
①值传递:实参是变量(a)或者变量地址(&a),传递的是值;
②地址传递:实参是变量名,传递的是变量的地址。
//引用
int main(int argc, char* argv[])
{
void max(int &, int &);//函数声明
int a = 3, b = 5;//变量定义
int &m = a, &n = b;//声明m,n是a,b的引用
max(a, b);//实参是变量名,传递的是地址
cout << "a = " << a << endl;
cout << "b = " << b << endl;
return 0;
}
void max(int &p1, int &p2)//声明m,n是引用
{
int m;
if(p1 > p2)
{
m = p1;
p1 = p2;
p2 = m;
}
}
总结
①指针时地址,存储指针值得变量称为指针变量,它的类型称为指针类型,其实时指向类型;
②指针既然是地址,那它的指向相当灵活,这是在定义的时候就确定,在操作的时候体现的,难的是他的操作;
③引用记住是地址传递,实参是变量名,传递的是地址,注意和值传递的区别(这个后续再看看怎么用。