C语言声明
C语言的声明可以嵌套,所以可以构成一些相当复杂的嵌套,这种过于复杂的声明并不推荐,但是我们应该学会如何去阅读这些声明。并在后面的部分会讲述如何利用typedef去简化这些声明
left-right rule
"left-right" rule
是解读C语言声明的强力规则,掌握这个规则足够让我们解决几乎所有的C声明,而且规则并不复杂,分为下面几步
- 从变量名开始阅读
- 从包含变量名的最内部的括号由内而外解释,对于每个括号的内部,先往右边解释,再往左边解释,层层解释> 直至结束。
第一条规则告诉你如何开始,第二条规则告诉你如何解释,然后,我们要记住一些规则,你可以理解为这是规定的内容,是死的。
*
读作 “指向…的指针” ,接下里的标识符修饰指针指向的内容[]
读作 “…的数组” ,接下来的标识符修饰数组的元素()
读作 “返回…的函数”,接下来的标识符修饰函数返回值
读作...
是用语言的方式去将声明一层层定义出来,就是反映了接下来修饰的内容,理解后面的部分我认为会更加直接。
好的,看了上面的内容你大概了还是相当模糊的,不要急,上面的内容暂时不必关系,下面我会开始列举一些例子,可以一边看例子一边看解释规则,如果你想自己尝试一下也可以先自己解读。
先来看一点我们以前可能遇到的头疼声明
1 | 1. const int * a; |
不知道你是否还记得结果,在这三个声明中,只有3的指针是只读的,也就是指针常量
,其它两个都是指针指向的对象是只读的,也就是常量指针
。我们来一个个解读。
const int * a
:
-
先找到变量名a
-
无括号,总共就一层,先往右看,没有任何标识符
-
往左看,首先我们看到的是一个
*
,OK,那么此时a
已经是一个指针
了,接下来的标识符只会修饰指针指向的内容,这一点很关键
-
继续往左看,看到
int
,说明指针指向一个int
类型的变量 -
再看到
const
,表明该int
类型的变量是一个常量
那么这个声明的结果就是变量a是一个指针,指针指向的内容是一个int类型的常量
int const * a
:
- 找到变量名a
- 往右看,无标识符
- 往左看,
*
,a是一个指针 - 往左看,
const
,指向的变量是一个常量 - 往左看,
int
,该常量是int类型的
那么这个声明的结果就是变量a是一个指针,指针指向的内容是一个int类型的常量
int * const a
:
- 找到变量名a
- 往右看,无标识符
- 往左看,
const
,a是常量 - 往左看,
*
,a是一个指针,也就是a现在是一个常量指针,接下来的标识符会开始修饰指针指向的变量 - 往左看,
int
,指针指向的变量是int类型的
那么这个声明的结果就是变量a是一个常量指针,指针指向的内容是一个int类型的变量
从上面的例子中,有两点很重要
- 按照顺序读取
- 一定要理解标识符什么时候修饰变量a,什么时候修饰a指向的内容,当a被定义为指针以后,标识符就只会修饰指针指向的内容,这是一个固定的规则
再来看一个容易混淆的例子
1 | 1. int *p[10]; |
1是指针数组,2是数组指针
int *p[10]
:
- 找到变量名p
- 往右看,
[10]
,p是一个数组,接下来标识符只能修饰数组元素 - 往左看,
*
,数组元素是指针 - 往左看,
int
,指针指向的变量是int类型
int (*p)[10]
:
- 找到变量名p
- 因为有括号,所以要先处理
(*p)
- 往右看,无标识符
- 往左看,
*
,p是一个指针,接下来的标识符只会修饰指针指向的内容 - 解释完括号内部的,现在解释外部的,
int ()[10]
- 往右看,
[10]
,指针指向的变量是数组类型的 - 往左看,
int
,数组的元素是int类型
我们继续来看一点复杂一些的例子
1 | char * const *(*b)(int a); |
- 找到变量名b
- 有括号,我们要先解释
(*b)
*b
往右看,无标识符- 往左看,b是一个指针
- 然后再解释
char * const * (int a)
- 往右看
(int a)
,b指向的变量是一个函数,参数是一个整型变量,接下来左边的标识符都只会修饰函数返回值 - 往左看,
*
,该函数的返回值是一个指针 - 往左看
const
,返回值的指针指向一个常量 - 往左看,
*
,这个常量是一个指针 - 往左看,
char
,指针常量指向一个char类型变量
那么这个声明的结果就是变量a是一个函数指针,函数的参数是一个整型变量,返回值是一个指针,指针指向一个指针常量,指针常量指向一个字符型变量
这里要说明一下,函数的参数也是要经过解释的,只不过这个例子和下面这个例子的函数参数都复杂我就直接跳过了。
1 | char *(* a[10])(int **p); |
- 找到变量名a
- 先处理
(* a[10])
- 往右看,
[10]
,a是一个数组,接下来的标识符只会修饰数组元素 - 往左看,
*
,数组元素是指针类型 - 接下来解释
char *()(int **p)
- 往右看,
(int **p)
,指针指向一个函数,参数是二级指针 - 往左看,
*
,返回值是一个指针 - 往左看,
char
,char类型指针
那么这个声明的结果就是变量a是一个数组,数组元素是函数指针,函数参数是二级指针,返回值一个指向字符型变量的指针
OK,如果你看完并理解了上面的内容,你会发现解释C的声明实际上也不是太复杂,我重新总结一下要点,我相信现在的你看这些要点会有新的感悟
- 按照顺序解释
- 记住现在解释的内容是什么,比如变量已经声明为指针后,要清楚接下来的标识符只会修饰指针指向的内容,而不会再来修饰这个指针本身。
虽然现在我们会解释C语言的声明,但是复杂的声明在绝大部分情况下都不是好的选择,接下里我们介绍用typedef来简化声明
用typedef来简化声明
typedef在我们初学C时其实并不太常用,大多数时候我们用来简化结构体的声明
1 | struct Node{ |
接下来我们就可以直接用Node
而不是struct Node
来定义变量,这种做法是否明智不是我们要讨论的内容,但是仅仅是这样的使用远远没有展现出typedef的力量。
typedef和#define的区别
如果我们仅仅只是像上面一样用过typedef,我们可能会对typedef的真实作用产生误区。
#define是完全的字符串替换,仅此而已,但是typedef从某种意义上来说,它真实的重命名了一个类型。
比如下面这个例子
1 |
|
变量a是指针
,而变量b只是一个普通的整型变量
,因为实际的定义是这样的
1 | int *a,b; |
但是c和d都是指针
,因为B是类型int *
的别名,是一个整体,可以说B本身就是一个新的类型。
上面的例子已经展现了typedef与字符串替换大不相同,但typedef不仅于此,如果你没有真正的明白typedef的作用,那么下面这个例子你一定无法理解
1 | typedef int Array[20]; |
此时的a是一个长度为20的整型数组。Array是一个独立的类型,这个类型是长度为20的整型数组。
现在你应该可以明白typedef的用法了,下面就来看看typedef怎么简化声明。
typedef简化声明
我们来简化上面的例子
char *(* a[10])(int **p);
1 | typedef char *(*Func)(int **p); |
我们用typedef把函数部分和数组部分拆分了开来,这些简化效果不是特别明显,我们来看更复杂一点的例子。
1 | char *(* (*a[10])[10][10])(int **p,char *(*f)(int *p[10])); |
这个例子已经毫无可读性了,也不需要耗费心神去解释,我们来简化这个声明。
1 | // 先简化作为参数的函数 |
可见typedef在复杂声明中的力量,当然,简化的方法可以按照你自己的想法任意的组织。
好了,本文主要是讲述了C语言的声明相关内容,希望能让你有所收获。