184. 指针

什么是指针

因为指针变量也是变量, 既然是变量, 那么这个变量也就有内存空间用来保存变量的值, 更准确的说, 有内存空间用来存放地址.

ptr 为指向了一块内存空间的指针

<==> ptr 存放了这块内存空间的首地址

<==> ptr 的值就是这块内存空间的首地址

如何理解指针

C 语言标准规定,对于一个符号的定义,编译器总是从它的名字开始读取,然后按照优先级顺序依次解析。

对,从名字开始,不是从开头也不是从末尾,这是理解复杂指针的关键!

有几种运算符的优先级非常容易混淆,它们的优先级从高到低依次是:

  1. 定义中被括号 () 括起来的那部分。
  2. 后缀操作符: 括号 () 表示这是一个函数,方括号 [] 表示这是一个数组。
  3. 前缀操作符: 星号 * 表示“指向 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

    1. 从p开始解析, p的左边是 *, 那么就知道p是一个指针, 但是还不知道p指向了什么类型的数据(指向什么类型的数据<==>可以存放什么类型数据的地址);
    2. 再看剩下的 int, 可知p是指向了一个int型的数据;
    3. 最后, 可以得出: p是一个指向一个int类型数据的指针, 也就是一维数组指针.
  • int **p

    1. 从p开始解析, p的左边是 *, 那么就知道p是一个指针, 但是还不知道p指向了什么类型的数据(指向什么类型的数据<==>可以存放什么类型数据的地址);
    2. 再往外剥一层, 还是 *, 那么可以知道, p指向了一个指针, 也就是说p是一个指针的指针;
    3. 再往外剥一层, 是 int, 那么可以知道, p指向的那个指针 指向的元素是int型数据, 也就是p指向了一个 int * 型的变量;
    4. 最后可以得出: p是一个指向了一个 int *类型的指针, 也就是二维数组指针.
  • int *p[6];

    1. 从p开始解析, p的第一层是 * [6], 根据优先级, 先解析右边的 [6], 可以知道p是一个6个元素大小的数组. 再解析左边的 *, 说明数组元素是一个指针, 但是指针指向的类型不清楚;

    2. 往外剥一层, 是 int, 可知: 数组元素指向的类型是int

    3. 最后可知: p是一个6个元素的数组, 其元素的类型是 int *, 也就是二维数组指针

      可以看成一个6行的表, 而表的列的长度是不定的

  • int (*p)[6];

    1. 从p开始解析, 先解析括号内部的第一层 (*p), 说明p是一个指针, 但是指向的类型不清楚

    2. 再往外剥一层, 解析的是 int [6]: 先解析 [6], 说明p指向的类型是一个大小为6个元素数组; 在解析 int, 说明数组元素的类型为 int 型

    3. 最后可知, p是一个指针, 指向了一个数组元素为int型且大小为6的数组.

      这里可以成是一个6列的表, 但是有多少行是不定的

  • int (*p4)(int, int);

  • char *(*c[10])(int **p);

    1. 先解析括号里面的, 即 *c[10]. 这里先解析 [10] 表明 c 是一个数组, 但是数组元素类型不清楚是什么; 再解析 *, 表明数组元素是指针, 但是指针指向的类型不清楚

    2. 再往外解析一层, 即解析 * (int **p). 这里先解析 (int **p), 其表明这是一个函数, 参数列表为 int **p; 再解析 *, 其表明函数返回值为一个指针, 但是指针指向的类型不清楚

    3. 再往外解析一层, 即解析 char, 其表明指针指向一个字符.

    4. 最后可知, c是一个数组, 数组元素为指针大小为10, 这些指针指向了一个函数, 函数原型为 * (int **p), 函数的返回值是一个指针, 指向了一个char型

  • int (*(*(*pfunc)(int *))[5])(int *);

    1. 先解析 *pfunc, 可知 pfunc 是一个指针

    2. 再往外解析一层, 即解析 * (int *), 这里先看 (int *), 其表明这是一个函数, 再看 *, 其表明函数的返回值是指针类型

      可知*(*pfunc)(int *)中 pfunc指向了一个函数, 即pfunc是一个函数指针, 该函数原型是* (int *), 该函数返回值是一个指针, 但是这个指针指向什么类型很不清楚, 那么剩余部分就是说明这个指针指向了什么类型

    3. 再往外解析一层, 即解析 * [5], 这里先看 [5], 表明这是一个数组, 再看 *, 其表明数组中的元素是指针, 但是指针指向的类型还不清楚

      可知 *(*(*pfunc)(int *))[5]中函数返回值这个指针指向了一个数组, 数组元素为指针, 但是指针指向了什么类型还得接着看

      这一步解决了函数返回值指向了什么类型, 这里的函数返回值指向了一个指针数组, 但是数组元素指向了什么类型, 需要看剩余的部分

    4. 再往外解析一层, 即解析 int (int *), 先解析 (int *) 表明这是一个函数, 再看 int, 其表明函数返回值是int型

      可知 int (*(*(*pfunc)(int *))[5])(int *) 中 pfunc 指向了一个函数, 函数的参数列表是 (int *), 返回值是一个指针, 它指向了一个数组元素为指针的数组, 该数组元素指向一个函数, 函数原型为 int (int *)