C 语言复杂声明

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

dcl: optional *'s direct-dcl(含有可选"*"的direct-dcl)
direct-dcl name
            (dcl)
            direct-dcl()
            direct-dcl[optional size] 

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

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

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类对象的函数的指针。

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

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*。

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

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

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

int (*func)(int *p);

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

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

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

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