C语言复杂声明

本文最后更新于:3 个月前

C语言常常因为声明的语法问题而受到人们的批评,特别是涉及到函数指针的语法。C语言的语法力图使声明和使用相一致。对于简单的情况, C语言的做法是很有效的,但是,如果情况比较复杂,则容易让人混淆,原因在于, C语言的声明不能从左至右阅读,而且使用了太多的圆括号。
在C中,声明的形式为(dcl是declaration的简写):

1
2
3
4
5
dcl: optional *'s direct-dcl(含有可选"*"direct-dcl)
direct-dcl name
(dcl)
direct-dcl()
direct-dcl[optional size]

简而言之,声明符dc1(可以理解成间接声明)就是前面可能带有多个*direcr-dclo direct-dcl可以是name、由一对圆括号括起来的dcl、后面跟有一对圆括号的direct-dcl、后面跟有用方括号括起来的表示可选长度的direc-dcl

根据该规则进行逆向解析,就可以得到正确的声明。简化一下:TypeName Declarator;其中,Declarator就是声明中的那个name。当你遇到任何你不能理解的声明时,这个法则就是救命稻草。最简单的例子:

1
int aInt;

这里,intTypeNameaIntDeclarator

再说明一下结合紧密度。在声或定义变量时,可以使用一些修饰比如*[]()等。()(非函数声明中的())具有最高的紧密度,其次才是函数和数组的()[]

没有*的声明称为直接声明(direct-dcl),而有*称为声明(dcl)。直接声明要比声明结合的紧。分解声明时,先读出结合紧的。在这里,我把direct-dcl称为更紧的结合,它比dcl结合得紧。

最后,需要你用英语来读出这个声明。对于[],应该读成array of

对于复杂的定义,可以将其分解。比如T (*p)()可以分解成T D1()D1读作:*function returning T。其中D1*p。那么该声明应该读成:p is a poniter to*。二者合在一起,就变成了 *p is a pointer to function returning T*,即:p是指向返回T类对象的函数的指针。

再看一个稍微复杂的示例:

1
T (*pfa[])();

根据dcldirect-dcl,可以分解成T1 D1(因为结合紧密度),T1也就是T (),那么应该读作:
*D1 is function returning T*。

D1又可以写成T2 D2,其中T2T1 [],可以分解成T1 D2[],读作:*array of D2 function returning T*。

D2是指针,读作:*pointers to。那么整个 T (*pfa[])() 应该读作:pfa is an array of pointers to function returning T*,即:pfa是个存放指向返回T类对象函数的指针的数组。

换种方式看,在这个例子中,pfa是名字,T(*[])()是类型。将(*pfa[])视为一体(direct-dcl),称为D1,那么可以写成T D1(),*function returning object of T*。在D1中,将*pfa视为一体(dcl),称为D2,那么*pfa[]应该是D2[](direct-dcl),array of D2。合起来就是 *array of D2 function returning object of T*。D2*pfa(dcl),替换到前面这句话,结果就是 *array of pointers to function returning object of T*。

有了这些说明,可以试着做一下下面的题,看看自己是否真的理解了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
char **argv
// argv: pointer to pointer to char
// 指向char型指针的指针
int (*daytab)[13]
// daytab: pointer to array[13] of int
// 指向int型数组的指针
int *daytab[13]
// daytab: array[13] of pointer to int
// 存放int型指针的数组
void *comp()
// comp: function returning pointer to void
// 返回值为指向void型指针的函数
void (*comp)()
// comp: pointer to function returning void
// 指向返回值为void型函数的指针
char (*(*x())[])()
// x: function returning pointer to array[] of
// pointer to function returning char
// 返回值为char型的函数
char (*(*x[3])())[5]
// x: array[3] of pointer to function returning
// pointer to array[5] of char

理解复杂声明可用的“右左法则”:从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。举例:

1
int (*func)(int *p);

首先找到变量名func,外面有一对圆括号,而且左边是一个*号,这说明func是一个指针;然后跳出这个圆括号,先看右边,又遇到圆括号,这说明(*func)是一个函数,所以func是一个指向这类函数的指针,即函数指针,这类函数具有int*类型的形参,返回值类型是int

1
int (*func[5])(int *);

func右边是一个[]运算符,说明func是具有5个元素的数组;func的左边有一个*,说明func的元素是指针(注意这里的*不是修饰func,而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合)。跳出这个括号,看右边,又遇到圆括号,说明func数组的元素是函数类型的指针,它指向的函数具有int*类型的形参,返回值类型为int

在C++中,规则比C要复杂一些。不过,基本思想保持不变,按照C的原则来理解复杂的声明,基本上就能满足要求了。没有在这里列出C++的规则一方面是因为太广,不能覆盖全;另一个原因就是,按照C的规则来就足够了,毕竟C++要与C兼容。