指针
一、要理解指针,必须弄清指针的三方面内容:
- 指针的类型;
- 指针所指空间存储数据的类型;
- 指针的值及存储指针值的空间大小;
我们通过例子进行说明:
(1) int *p; (2) char *p; (3) int **p; (4) int (*p)[3];
1、指针的类型
从语法的角度讲,只要把定义中的指针名字去掉,剩下的部分就是这个指针的类型,上例中各个指针的类型为:
(1) int *p; // 指针的类型是 int* (2) char *p; // 指针的类型是 char* (3) int **p; // 指针的类型是 int** (4) int (*p)[3]; // 指针的类型是 int (*)[3]
我们可以这么理解:一个基本的数据类型(包括结构体等自定义类型)加上“*”号就构成了指针的类型。这个类型定义的变量大小是一定的,与“*”号前面的数据类型无关。“*”号前面的数据类型只是说明指针所指向的内存里存储数据的类型。
2、指针所指空间存储数据的类型
当通过指针来访问指针所指向的内存区时,该类型决定了编译器将把那片内存区里的内容当做什么来看待。
从语法的角度来讲,只须把指针定义中的指针变量名和名字左边的指针声明符“*”去掉,剩下的就是指针所指空间存储数据的类型。
(1) int *p; // 指针所指空间存储数据的类型是 int (2) char *p; // 指针所指空间存储数据的类型是 char (3) int **p; // 指针所指空间存储数据的类型是 int* (4) int (*p)[3]; // 指针所指空间存储数据的类型是 int [3]
程序中使用指针是为了通过指针对指针所指空间存储的数据进行操作,所以指针所指空间存储数据的类型是我们更关心的。而对于编译器来说,需要根据该类型来确定读写从指针地址开始的多少个字节空间的数据。
3、指针的值及存储指针值的空间大小
指针的值是指针变量本身存储的数值,由于指针就是一个内存地址,所以指针的值也就是一个内存地址。在32位系统中,内存地址均为32位长,所以存储指针值的空间为4个字节。
二、指针的算术运算
指针的算术运算是指指针可以加上或减去一个整数,因为指针值是一个内存地址,所以其加、减一个整数的意义是:指针从当前位置向高地址方向(加运算)或向低地址方向(减运算)移动多少个元素,这种加减运算和通常的数值加减运算是不同的。
例如:
char s[] = {"ABCDEFGH"}; int *p; p = (int *)s; p++;
解析:
- 指针p的类型是 int*,它指向的类型是 int,
- 语句 p = (int *)s; 让p 指向 s ,并强制类型转换成 int*类型。
- 语句 p++; 并不是简单的 p中地址加1,而是系统会根据p指针所指元素的类型,让p指针指向下一个元素,即编译器把指针p的值加上1Xsizeof(int),在32位系统中,是被加上了4。
切记:指针加减一个整数n,并不是地址值简单加减n,具体加减多少,由指针所指类型确定,一般理解为指针向后或向前移动n个元素。
补充:
int a = 20; int *p = &a; cout << &a << " " << p << " " << *&p << endl;
其实输出的这三个数值一样的。
- &a 代表a的地址,
- p存储了a的地址,
- *&p:首先&p表示p变量本身的地址,再对(&p)取*操作,相当于对(&p)取所存储的值,由于(&p)是p变量本身的地址,它存储的就是a的地址,所以会输出a的地址。 分清一个是地址,一个是地址所存储的值。
2、数组的首地址和数组首元素的地址
在数组指针的应用中,经常会遇到一些容易混淆的问题,我们来看下面的例子:
int a[10] = {1,2,3,4,5,6,7,8,9,10}; int *p1; int (*p2)[10]; p1 = a; p2 = &a; printf("%d\n", *p1); printf("%d\n", *(int *)p2); printf("%d\n", *(p1+1)); printf("%d\n", *(int *)(p2+1));
程序中定义的 a为具有10个元素的数组,数组元素类型为整型;p1为指向整型的指针;p2为指针变量,指向具有10个元素的数组,数组元素类型为整型。
- p1 = a; 取数组a 的首元素地址赋值给p1,因为p1为 int*类型,数组 a的首元素 a[0]为整型,其地址类型为 int*,二者类型相同,可以赋值;
- p2 = &a; 取数组a 的首地址赋值给p2 ,因为 p2为 int(*)[10]类型,而数组a为具有10个整型元素的数组,其地址类型也为 int(*)[10],二者类型相同,可以赋值。
- 我们知道,在C语言中,赋值运算符 = 号两边的数据类型必须相同,如果不同需要显式或隐式的类型转换。
接下来,我们来分析输出结果:
- printf("%d\n", *p1); 输出的是从p1 所指字节开始的一个整数,也就是数组 a 的第一个元素a[0]的值,结果为1;
- printf("%d\n", *(int *)p2); 输出的是将p2 强制转换成(int*)后,即将p2 看成是指向整型的指针时,从p2 所指字节开始的一个整数,也就是数组 a 的第一个元素a[0]的值,结果为1;
- printf("%d\n", *(p1+1)); 输出p1 指针所指的下一个元素的值,由于 p1为指向整型的指针,所以p1+1指向下一个整数,即指向数组a的第2个元素a[1],输出结果为2;
- printf("%d\n", *(int *)(p2+1)); 由于p2是指向具有10个整型元素数组的指针,所以p2+1指向的下一个元素,就是跳过这10个元素的开始位置,即数组a中最后一个元素a[9]之后的存储单元。其结果是不可预知的。
上题中a代表数组首元素的地址,而&a代表数组的首地址,虽然他们首地址都是指一个存储单元,但是在运算过程中,他们的基本结构就不同啦。