声明是C语言中一个非常基础但重要的部分,无论是阅读他人的代码,还是排查编译报错,正确理解声明都会对我们有莫大的帮助。
有的人可能会说声明不是很简单吗?
小A说,看,我声明了一个整型变量:
int a;
小B说,瞧,我声明了一个函数:
int func(void);
我想大家理解上面的声明肯定是手拿把掐,轻而易举。
那么,下面几个声明呢?
int* arr[10];
int (*arr_ptr)[10];
int *func(int a);
int (*func_ptr)(int a);
以及这个呢?
int (*func_arr[5])(int a, int b);
如果你有点拿不准了,那就往下看吧,本文的目的就是给大家分享一下解析声明的方法,从而使得各位更好地理解 C 语言中的声明。
一、认识声明
首先让我们来看下声明的组成,一般来说,声明具体如下格式:
声明说明符 声明符;
声明说明符描述的是声明的数据项的性质,声明符描述的则是数据项的名字,并且描述了数据项性质的额外特点。
声明说明符有三大类:
- 存储类型:
auto
,static
,extern
,register
- 类型限定符:
const
,volatile
- 类型说明符:
void
,char
,short
,int
…
为了让大家便于理解,本文只讨论仅带类型说明符的声明,我想这是大家最熟悉的部分,存储类型与类型限定符我们以后再讨论。
接下来,让我们来认识一下声明符:声明符是由标识符、*
、[]
、()
组成的。
-
标识符:声明的变量或者函数的名字
int a;
这是最简单的声明符,其中a是标识符,由于没有其他符号,因此这个标识符a就是声明符。
-
*
:如果在标识符前加一个*
号,那么这个声明符表示指针int *p;
-
[]
:如果在标识符后加一个[]
,那么这个声明符表示数组int arr[10];
-
()
: 如果在标识符后加一个()
,那么这个声明符表示函数int add(int a, int b);
上面列出的几个声明符都很简单,但是我们在实际工作中遇到的声明符往往是标识符、*
、[]
、()
的组合,也就是复杂声明符。接下来,让我们来看看如何分析复杂声明符。
二、声明的解析规则
有两条简单的规则可以帮助我们理解所有的声明,好的,划重点了:
第一条规则:由内而外
这条规则的含义就是,首先定位声明中的标识符,并由此开始解析
第二条规则:遇事不决找括号
当我们运用第一条规则找到标识符后,我们往往会发现标识符的左右两边有其他符号,这时候我们就会面临一个选择:向左走?向右走?
第二条规则为我们指明了方向,那就是找括号,即先
()
和[]
,再*
举个栗子,让我们来分析如下声明:
int* arr[10];
根据第一条规则,我们首先找到标识符arr
,然后从此处开始解析。
我们观察到在arr
的左边是*
,右边则是[10]
,那么arr是指针还是数组呢?
让我们运用第二条规则,先将arr
与[10]
进行组合,这么一来**,arr
的性质就确定了,它是一个数组**。
既然它是一个数组,那么就需要说明数组元素的类型和数量,数量我们已经知道了,10个,那么数组元素是什么类型呢?
根据第二条规则,接下来要解析*
了,这说明数组元素的类型是指针,那么是什么类型的指针呢?int
。因此,我们知道了数组元素的类型是整型指针。
综合起来,arr是一个包含10个元素的数组,每个元素的都是整型指针。
接着,我们来解析这条声明:
int *func(int a);
同样的,首先找到标识符,func
,然后观察func
的两边,发现左边是*
,右边是(int a)
。
那么根据第二条规则,我们应该先将func
与(int a)
结合,如此一来,func
的性质就确定了,它是一个函数。
既然是函数,那就要有参数和返回类型,根据(int a)
我们可以知道,这个函数的接受一个整型参数。
对于返回类型,我们可以通过解析*
得知,该函数的返回类型是一个指针,而指针的类型则是int
,即整型指针。
综合起来,func是一个函数,它接收一个整型参数,返回类型是整型指针。
三、攻克复杂声明符
大家估计看出来了,第二条规则其实就是优先级的运用,因为()
与[]
的优先级比*
更高,所以我们先解析()
和[]
,再解析*
。
但是我们知道,()
可以改变优先级,比如下面的声明:
int (*func_ptr)(int a, int b);
在上面的声明中,标识符func_ptr
的左边是*
,右边是(int a, int b)
,按照规则,应该先将func_ptr
与(int a, int b)
结合,从而将func_ptr
定性为函数。
但是,func_ptr
与*
被()
包了起来,因此优先级被提高了,所以我们要先将func_ptr
与*
号进行结合,因此func_ptr
应该被定性为指针。
接着,我们要解析(int a, int b)
,从而可以确定func_ptr
指向的是一个接受两个整型参数的函数,最后根据开头的int
确定函数的返回值为整型。
综合起来就是,func_ptr是一个指向函数的指针,该函数接受两个整型参数的函数,返回类型是整型。
这种指针,我们一般叫它函数指针。
好了,有了以上经验,我们可以来解析本文的最终Boss了:
int (*func_arr[5])(int a, int b);
解析步骤如下:
- 找到标识符
func_arr
func_arr
左边是*
,右边是[5]
,因此先将func_arr
与[5]
结合,从而将func_arr
定性为数组,元素个数为5
func_arr[5]
的左边是*
,右边是(int a, int b)
,但是func_arr[5]
与*
被()包起来了,所以先解析*
,从而确定数组的元素的类型是指针- 接下来解析
(int a, int b)
,可以确定指针指向的是一个接收两个整型参数的函数 - 最后根据开头的
int
确定函数的返回类型是整型
综合起来,func_arr是一个包含5个元素的数组,每个数组元素都是函数指针,指向的函数接受两个整型参数,返回类型是整型。
结语
希望通过本文的实例分析,可以帮助大家更好地理解和应用 C 语言中的各种声明语句。
若有错漏之处,欢迎大家交流指正~
最后,欢迎大家关注我的公众号《嵌入式3分钟》,一起学习嵌入式!
参考文献
[1] K.N.King, C语言程序设计:现代方法(第2版), 吕秀锋, 黄倩译,人民邮电出版社, 2010.