# 快速搞懂 C/C++ 指针声明
面试高频指数:★★★★☆
很多小伙伴,看到一些复杂的类型声明就看不懂到底是什么类型了,比如下面这个
int (*(*foo)[5])(int);
接下来我们就带大家如何去看懂一个复杂的声明:
# 一、复杂类型说明
要了解指针,多多少少会出现一些比较复杂的类型。所以先介绍一下如何完全理解一个复杂类型。
要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样。
所以总结了一下其原则:从变量名处起,根据运算符优先级结合,一步一步分析。
下面让我们先从简单的类型开始慢慢分析吧。
# 1.1 普通变量
int p;
这是一个普通的整型变量。
即 p is int.
# 1.2 普通指针
int *p
首先从 p 处开始,先与 * 结合,所以说明 p 是一个指针。然后再与 int 结合,说明指针所指向的内容的类型为int型,所以 p 是一个指向整型数据的指针。
即 p is pointer to int.
# 1.3 数组
int p[3];
首先从 p 处开始,先与[]结合,说明 p 是一个数组。然后与 int 结合,说明数组里的元素是整型的,所以 p 是一个由整型数据组成的数组。
即:p is arry(size 3) of int.
# 1.4 指针数组
int *p[3];
首先从 p 处开始,先与 [] 结合,因为其优先级比高( [] 在c语言中属于后缀运算符和 () 等同为最高优先级),所以 p 是一个数组。然后再与 * 结合,说明数组里的元素是指针类型。之后再与int结合,说明指针所指向的内容的类型是整型的,所以 p 是一个指向 int 的指针数组。
英文即: p is arry(size 3) of pointer to int.
# 1.5 数组指针
int (*p)[3];
首先从 p 处开始,先与 * 结合(因为 * 是被括号包围的),说明 p 是一个指针。然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组。之后再与 int 结合,说明数组里的元素是整型的,大小是 3,所以 p 是一个指向 int 数组(大小为3)的指针。
英文即:p is pointer to arry(size 3) of int.
# 1.6 二级指针
int **p;
首先从 p 开始,先与 * 结合,说明 p 是一个指针。然后再与 * 结合,说明指针所指向的元素还是指针。之后再与 int 结合,说明该指针所指向的元素是整型数据。由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针。
英文即: p is pointer to pointer to int.
# 1.7 函数声明
int p(int);
从 p 处起,先与 () 结合,说明 p 是一个函数。然后进入 () 里分析,说明该函数有一个整型变量的参数,之后再与外面的 int 结合,说明函数的返回值是一个整型数据。
英文即: p is function(int) returning int.
# 1.8 函数指针
int (*p)(int);
从 p 处开始,先与指针结合,说明 p 是一个指针。然后与()结合,说明指针指向的是一个函数。之后再与()里的int结合,说明函数有一个int型的参数,再与最外层的int结合,说明函数的返回类型是整型,所以 p 是一个指向有一个整型参数且返回类型为整型的函数的指针。
英文即: p is pointer to function(int) returning int.
# 1.9 复杂声明
int* (*p(int))[3];
从 p 开始,先与()结合,说明 p 是一个函数。然后进入()里面,与int结合,说明函数有一个整型变量参数。然后再与外面的 * 结合,说明函数返回的是一个指针。
之后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组。接着再与结合,说明数组里的元素是指针,最后再与int结合,说明指针指向的内容是整型数据。
所以 p 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数。
p in function(int) returning pointer to arry(size 3) of pointer to int.
(到这估计已经快晕了)
说到这里也就差不多了。理解了这几个类型,其它的类型对我们来说也是小菜了。
不过一般不会用太复杂的类型,那样会大大减小程序的可读性,请慎用。这上面的几种类型已经足够我们用了。
# 二、总结
简单总结下如何解释复杂一点的 C 语言声明(暂时不考虑 const 和 volatile):
# 2.1 指针声明阅读顺序
1. 先抓住 标识符(即变量名或者函数名)
2. 从距离标识符最近的地方开始,按照优先顺序解释派生类型(也就是指针、数组、函数),顺序如下:
- 用于改变优先级的括弧
- 用于表示数组的[],用于表示函数的()
- 用于表示指针的
3. 解释完成派生类型,使用“of”、“to”、“returning”将它们连接起来。
4. 最后,追加数据类型修饰符(一般在最左边,int、double等)。
数组元素个数和函数的参数属于类型的一部分。应该将它们作为附属于类型的属性进行解 释。
# 2.2 举例
比如我们上面提到的一个例子
int (* p)(int);
- 抓住 p,即 p is
- 抓住改变优先级的 (),括号里是 * ,也就是指针,即 p is pointer to
- 再看表示函数的 (),即 p is pointer to function(int) returning, 记得 函数的参数类型属于函数的一部分
- 最后看左边的类型为 int,即 p is pointer to function(int) returning int
翻译为中文就是:
p 是一个指向<参数为 int 返回int> 的函数指针。
# 2.3 复杂声明请使用 typedef
实际上,实际开发中,根本用不上非常复杂的声明,如果你写了,绝对会被维护代码的同事诅咒。
所以对于实在需要嵌套很多层的复杂声明,请使用 typedef 来肢解复杂的声明。
说白了,就是将复杂的声明,先变为一个个简单的声明,然后取一个别名。
还是拿这个例子来说:
int* (*p(int))[3];
一般来说,应该没有多少同学能正确的识别出上面这个 p 到底是个什么东西。
那根据我们上面的解释,p 是一个函数,只是它的返回值是一个指针,指向的是 <int 指针变量组成的数组>
那我们就用 typedef 把 <int 指针变量组成的数组> 取一个别名:
typedef int* SB[3];
假设就叫 SB。
那么现在那个复杂的声明就变简单了:
SB *p(int);
这一个大家应该都认识吧,
p 是一个函数,函数返回一个指针,指针的类型是 SB。。。。
还有比这个更复杂的声明,比如 signal 函数
C 库函数 void (*signal(int sig, void (*func)(int)))(int) 设置一个函数来处理信号,即带有 sig 参数的信号处理程序。
大家可以尝试着看下这个函数怎么读,实际上拆解开了挺简单的,就是需要你传入一个回调函数,但是写在一起就是特别复杂。
人类天生不适合解读那么复杂的声明,所以那就分层,直到我们能一眼看出,那么就ok~
最新原创的文章都先发布在公众号,欢迎关注哦~,
扫描下方二维码回复「CS」可以获得我汇总整理的计算机学习资料~