如何读懂复杂的C声明
K&R曾经承认:“C语言声明的语法有时会带来严重的问题”。的确,诸如 char * const * ( * next ) ( ) ; char * ( * c[10]) ( int ** p );之类的声明晦涩难懂,让人迷茫。还好,Peter Van Der Linden在它的经典著作《Expert C Programming》中介绍了两种解开这个难题的方法。
下面介绍读懂C语言声明两法:
一、优先级法。
A 声明从它的名字开始读取,然后按照优先级顺序依次读取。
B 优先级从高到低依次是:
B. 1 声明中被括号括起来的部分
B. 2 后缀操作符:
括号 () 表示这是一个函数
方括号 [] 表示这是一个数组
B. 3 前缀操作符:星号 * 表示 “指向...的指针”
C 如果const和(或)volatile关键字的后面紧跟类型说明符(如int,long等),那么它作用于类型说明符。在其他情况下,const和(或)volatile关键字作用于它左边紧邻的指针星号。
举例说明此法: char * const * (*next)();
A 首先,看变量名next,并注意到它直接被括号所括住
B.1 所以先把括号里的东西看为一个整体,得出“next是一个指向...的指针”
B 然后考虑括号外的东西,在星号前缀和括号后缀之间做出选择
B.2 B.2规则告诉我们优先级较高的是右边的函数括号,所以得出“next是一个函数指针,指向一个返回...的函数”
B.3 然后,处理前缀*,得出指针所指的内容
C 最后,把 char * const 解释为指向字符的常量指针
概括分析之后,这个声明表示:“next是一个指针,它指向一个函数,该函数返回另一个指针,该指针指向一个类型为char的常量指针”。
二、符号法。
步骤如下:
1、取最左边的标识符。
2、查看标识符右边的下一个符号,如果是方括号 [ ,则对于每一对,表示“...的数组”
3、如果是左括号(,则到右括号为止的内容表示“返回...的函数”
4、如果左边的符号是一个左括号(,这个括号把已经处理的部分声明组合在一起,直到遇见对应的右括号。然后从第2步重新开始。
5、如果左边的符号是下述之一:
const volatile *
继续向左读取符号,知道所读符号不再是上边那三个之一。如果符号是const,表示“只读”,如果是volatile表示“volatile”,如果是*,表示“指向...的指针”然后重复第4步。
6、剩下的符号形成声明的基本类型,剩余的符号一并阅读,如 static unsigned int
举例说明此法:char * (* c[10] ) (int **p);
剩余的声明 所采取的下一步骤 结果
char * (* c[10])(int **p) 第1步 表示“c是...”
char * (* [10])(int **p) 第2步 匹配,表示“c是...的数组”转下一步
char * (* )(int **p) 第3、4步 不匹配,转到下一步
char * (* )(int **p) 第5步 与星号匹配,表示“指向...的指针”,转第4步
char * ( )(int **p) 第4步 与“(”匹配,转到第2步
char * (int **p) 第2步 不匹配,转下一步
char * (int **p) 第3步 匹配,表示“返回...的函数,这个函数以int **p为参数”
char * 第4步 不匹配,下一步
char * 第5步 匹配,表示“指向...的指针”,剩下char,执行第6步
char 第6步 直接阅读
拼在一起,读作:“c是一个数组,它的元素是指向函数的指针,该函数以int **p为参数,返回指向char的指针”。大功告成了!
事实上,以上的解析过程完全可以写成一个程序,用来解析C声明,比如unix中的cdecl。