184. 指针
什么是指针
因为指针变量也是变量, 既然是变量, 那么这个变量也就有内存空间用来保存变量的值, 更准确的说, 有内存空间用来存放地址.
设 ptr
为指向了一块内存空间的指针
<==> ptr
存放了这块内存空间的首地址
<==> ptr
的值就是这块内存空间的首地址
如何理解指针
C 语言标准规定,对于一个符号的定义,编译器总是从它的名字开始读取,然后按照优先级顺序依次解析。
对,从名字开始,不是从开头也不是从末尾,这是理解复杂指针的关键!
有几种运算符的优先级非常容易混淆,它们的优先级从高到低依次是:
- 定义中被括号
()
括起来的那部分。 - 后缀操作符: 括号
()
表示这是一个函数,方括号[]
表示这是一个数组。 - 前缀操作符: 星号
*
表示“指向 xxx 的指针”。
指针到底是几维的
一个 int *p
, p指向的数据类型是 int 型, 那么其可以存储一个int型变量的地址. 因此可以这么说: 一维(数组)指针实际指向的是一个基本类型.
延伸一下: 为了能够通过指针来遍历数组元素,在定义数组指针时需要进行降维处理, 例如:
- 三维数组指针实际指向的数据类型是二维数组;
- 二维数组指针实际指向的数据类型是一维数组;
- 一维数组指针实际指向的是一个基本类型;
- 在表达式中,数组名也会进行同样的转换(下降一维).
反过来讲, 如果一个指针指向了一个N维数组, 那么这个指针就被称作 (N+1)维(数组)指针
这里数据类型就跟着最近的符号走
注意
还是以 int *p
为例, 这里p指向了一个int这一基本类型的变量, 那么代表p存储了这个变量的地址. 在指针维数上看, 这个p是指向了一个int型变量; 从p本身的数据类型上看, p的数据类型是 int *
, 而数据类型表明了这个变量有什么属性, 同时可以进行什么行为/操作. 那么, p既然可以有自己的运算法则, 那么就为p设计一套运算法则, 可以使用p进行++
等操作, 这样, p就可以完成使用指针来遍历数组元素. 个人认为是p的行为是的能够使用指针来遍历数组, 是p的指针维数确定了p可以操作什么维度什么样的数组.
而为什么叫一维数组指针呢? 在上面知道可以通过p完成对数组元素的遍历, 而有因为p指向了一个int型元素, 那么可知p可以遍历一组int型元素, 不就是一维数组了嘛!
示例
int *p
- 从p开始解析, p的左边是
*
, 那么就知道p是一个指针, 但是还不知道p指向了什么类型的数据(指向什么类型的数据<==>可以存放什么类型数据的地址); - 再看剩下的 int, 可知p是指向了一个int型的数据;
- 最后, 可以得出: p是一个指向一个int类型数据的指针, 也就是一维数组指针.
- 从p开始解析, p的左边是
int **p
- 从p开始解析, p的左边是
*
, 那么就知道p是一个指针, 但是还不知道p指向了什么类型的数据(指向什么类型的数据<==>可以存放什么类型数据的地址); - 再往外剥一层, 还是
*
, 那么可以知道, p指向了一个指针, 也就是说p是一个指针的指针; - 再往外剥一层, 是
int
, 那么可以知道, p指向的那个指针 指向的元素是int型数据, 也就是p指向了一个int *
型的变量; - 最后可以得出: p是一个指向了一个
int *
类型的指针, 也就是二维数组指针.
- 从p开始解析, p的左边是
int *p[6];
从p开始解析, p的第一层是
* [6]
, 根据优先级, 先解析右边的[6]
, 可以知道p是一个6个元素大小的数组. 再解析左边的*
, 说明数组元素是一个指针, 但是指针指向的类型不清楚;往外剥一层, 是
int
, 可知: 数组元素指向的类型是int最后可知: p是一个6个元素的数组, 其元素的类型是
int *
, 也就是二维数组指针可以看成一个6行的表, 而表的列的长度是不定的
int (*p)[6];
从p开始解析, 先解析括号内部的第一层
(*p)
, 说明p是一个指针, 但是指向的类型不清楚再往外剥一层, 解析的是
int [6]
: 先解析[6]
, 说明p指向的类型是一个大小为6个元素数组; 在解析int
, 说明数组元素的类型为 int 型最后可知, p是一个指针, 指向了一个数组元素为int型且大小为6的数组.
这里可以成是一个6列的表, 但是有多少行是不定的
int (*p4)(int, int);
char *(*c[10])(int **p);
先解析括号里面的, 即
*c[10]
. 这里先解析[10]
表明 c 是一个数组, 但是数组元素类型不清楚是什么; 再解析*
, 表明数组元素是指针, 但是指针指向的类型不清楚再往外解析一层, 即解析
* (int **p)
. 这里先解析(int **p)
, 其表明这是一个函数, 参数列表为int **p
; 再解析*
, 其表明函数返回值为一个指针, 但是指针指向的类型不清楚再往外解析一层, 即解析
char
, 其表明指针指向一个字符.最后可知, c是一个数组, 数组元素为指针大小为10, 这些指针指向了一个函数, 函数原型为
* (int **p)
, 函数的返回值是一个指针, 指向了一个char型
int (*(*(*pfunc)(int *))[5])(int *);
先解析
*pfunc
, 可知 pfunc 是一个指针再往外解析一层, 即解析
* (int *)
, 这里先看(int *)
, 其表明这是一个函数, 再看*
, 其表明函数的返回值是指针类型可知
*(*pfunc)(int *)
中 pfunc指向了一个函数, 即pfunc是一个函数指针, 该函数原型是* (int *)
, 该函数返回值是一个指针, 但是这个指针指向什么类型很不清楚, 那么剩余部分就是说明这个指针指向了什么类型再往外解析一层, 即解析
* [5]
, 这里先看[5]
, 表明这是一个数组, 再看*
, 其表明数组中的元素是指针, 但是指针指向的类型还不清楚可知
*(*(*pfunc)(int *))[5]
中函数返回值这个指针指向了一个数组, 数组元素为指针, 但是指针指向了什么类型还得接着看这一步解决了函数返回值指向了什么类型, 这里的函数返回值指向了一个指针数组, 但是数组元素指向了什么类型, 需要看剩余的部分
再往外解析一层, 即解析
int (int *)
, 先解析(int *)
表明这是一个函数, 再看int
, 其表明函数返回值是int型可知
int (*(*(*pfunc)(int *))[5])(int *)
中 pfunc 指向了一个函数, 函数的参数列表是(int *)
, 返回值是一个指针, 它指向了一个数组元素为指针的数组, 该数组元素指向一个函数, 函数原型为int (int *)