1、指针类型辨析
优先级顺序:[]
> *
int p
:从 p 开始,与 int 结合,表示 p 是 int 类型的变量。
int *p
:从 p 开始,先与 *
结合,表示 p 为指针;再与 int
结合,表示指针指向的是 int
类型变量。即:p 是指向 int
类型变量的指针。
int p[3]
:从 p 开始,先与 []
结合,表示 p 是数组;再与 int
结合,表示数组元素是 int
类型的变量。即:p 是元素为 int
类型变量的数组。
int *p[3]
:从 p 开始,先与 []
结合,表示 p 是数组;再与 *
结合,表示数组元素是指针;最后和 int
结合,表示指针指向的是 int
类型变量。即:p 是元素为 int
类型指针的数组。
int (*p)[3]
:从 p 开始,先和 *
结合,表示 p 是指针;然后和 []
结合,表示指针指向的是数组;最后和 int
结合,表示数组元素是 int
类型的变量。即:p 是指向 int
类型数组的指针;
int **p
:从 p 开始,先和 *
结合,表示 p 是指针;然后又和 *
结合,表示指针指向的是指针;最后和 int
结合,表示指针指向的是 int
类型变量。即:p 是指向 “指向 int
类型变量的指针” 的指针。
int p(int)
:从 p 开始,先与 ()
结合,表示 p 是函数,括号内部是 int
,表示函数入参为 int
类型变量,外侧 int
表示函数的返回值是 int
类型变量。即:p 是入参为 int
类型变量、返回值为 int
类型变量的函数。
int (*p)(int)
:从 p 开始,先和 *
结合,表示 p 是指针;然后和 int (int)
结合,表示入参为 int
类型变量、返回值为 int
类型变量的函数。即:p 是指向 “入参为 int
类型变量、返回值为 int
类型变量的函数” 的指针。
int *(*p(int))[3]
:从 p 开始,先和 (int)
结合,表示 p 是入参为 int
类型变量的函数;接着和 *
结合,表示函数返回的是指针;然后和 [3]
结合,表示指针指向的是 3 个元素的数组;接着和 *
结合,表示数组元素是指针;最后和 int
结合,表示指针指向 int
类型变量。即:p 是一个函数,函数入参为 int
类型变量,返回值是指针,指向元素个数为 3、元素类型为 int *
的数组。
2、指针的含义
指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。
要搞清一个指针需要搞清指针的四方面的内容:指针的类型、指针所指向的类型、指针的值或者叫指针所指向的内存区、指针本身所占据的内存区。
2.1 指针的类型
从语法的角度看,只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。例如:
int *ptr
:指针的类型是 int *
;
char *ptr
:指针的类型是 char *
;
int **ptr
:指针的类型是 int **
;
int(*ptr)[3]
:指针的类型是 int(*)[3]
;
int *(*ptr)[4]
:指针的类型是 int *(*)[4]
。
2.2 指针所指向的类型
当通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
从语法上看,只须把指针声明语句中的指针名字和名字左边的指针声明符 *
去掉,剩下的就是指针所指向的类型。例如:
int *ptr
:指针所指向的类型是 int
;
char *ptr
:指针所指向的的类型是 char
;
int **ptr
:指针所指向的的类型是 int*
;
int(*ptr)[3]
;:指针所指向的的类型是 int()[3]
;
int *(*ptr)[4]
:指针所指向的的类型是 int *()[4]
;
在指针的算术运算中,指针所指向的类型有很大的作用。
指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。
当对C 越来越熟悉时,你会发现,把与指针搅和在一起的"类型"这个概念分成"指针的类型"和"指针所指向的类型"两个概念,是精通指针的关键点之一。
2.3 指针的值(或者叫指针所指向的内存区或地址)
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。
在32 位程序里,所有类型的指针的值都是一个32 位整数,因为32 位程序里内存地址全都是32 位长。
指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)
的一片内存区。
以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX 为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。
指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在 int *ptr
中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。
以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?
2.4 指针本身所占据的内存区
指针本身占了多大的内存?你只要用函数 sizeof(指针的类型)
测一下就知道了。
在32 位平台里,指针本身占据了4 个字节的长度。
指针本身占据的内存这个概念在判断一个指针表达式(后面会解释)是否是左值时很有用。
3、指针的算数运算
指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的,以单元为单位。例如:
char a[20];
int *ptr=(int *)a; //强制类型转换并不会改变 a 的类型
ptr++;
指针 ptr 的类型是 int *
,它指向的类型是int
,它被初始化为指向整型变量a
。接下来的第3句中,指针 ptr 被加了1,编译器是这样处理的:它把指针 ptr 的值加上了sizeof(int)
,在32 位程序中,是被加上了4,因为在32 位程序中,int
占4 个字节。由于地址是用字节做单位的,故 ptr 所指向的地址由原来的变量a
的地址向高地址方向增加了4 个字节。
由于char
类型的长度是一个字节,所以,原来ptr 是指向数组a
的第0 号单元开始的四个字节,此时指向了数组a
中从第4 号单元开始的四个字节。
再看一个例子:
char a[20]="You_are_a_girl";
int *ptr=(int *)a;
ptr+=5;
在这个例子中,ptr 被加上了5,编译器是这样处理的:将指针ptr 的值加上5 乘sizeof(int)
,在32 位程序中就是加上了5 乘4等于20。由于地址的单位是字节,故现在的 ptr 所指向的地址比起加5 后的ptr 所指向的地址来说,向高地址方向移动了20 个字节。
在这个例子中,没加5 前的ptr 指向数组a
的第0 号单元开始的四个字节,加5 后,ptr 已经指向了数组a
的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是可以的。这也体现出了指针的灵活性。如果上例中,ptr 是被减去5,那么处理过程大同小异,只不过ptr 的值是被减去5 乘sizeof(int)
,新的ptr 指向的地址将比原来的ptr 所指向的地址向低地址方向移动了20 个字节。
4、运算符&和*
&
是取地址运算符,*
是间接运算符。
&a
的运算结果是一个指针,指针的类型是a
的类型加个*
,指针所指向的类型是a
的类型,指针所指向的地址嘛,那就是a
的地址。
*p
的运算结果就五花八门了。总之*p
的结果是p 所指向的东西,这个东西有这些特点:它的类型是p
指向的类型,它所占用的地址是p
所指向的地址。
5、指针表达式
一个表达式的结果如果是一个指针,那么这个表达式就叫指针表式。
int a,b;
int array[10];
int *pa;
pa = &a; // &a 是一个指针表达式。
Int **ptr = &pa; // &pa 也是一个指针表达式。
*ptr = &b; // *ptr 和&b 都是指针表达式。
pa = array;
pa++; // 这也是指针表达式。
由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,指针指向的内存区,指针自身占据的内存。
当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。如上一个例子,&a
不是一个左值,因为它还没有占据明确的内存。*ptr
是一个左值,因为*ptr
这个指针已经占据了内存,其实*ptr
就是指针pa
,既然pa
已经在内存中有了自己的位置,那么*ptr
当然也有了自己的位置。