本文共 4398 字,大约阅读时间需要 14 分钟。
在C语言面试中,经常会考查面试者对“C语言声明式”的理解,这类题型听起来就像绕口令一样,一不小心就容易将人绕到沟里,比如:
题1:说明如下四种声明式的区别
1)const char * p;
2)char const * p;
3)char * const p;
4)const char * const p;
题2:写出如下几种要求的声明式(或定义式)
1)一个有10个指针的数组tmp,其指针指向整形数;
2)一个指向有10个整形数数组的指针tmp;
3)一个指向函数的指针,该函数有一个整形参数并返回一个整形数;
4)一个有10个指针的数组,其指针指向一种函数,该型函数有一个int*参数并返回int;
5)一个指向有两个int形参并且返回一个函数指针的函数指针,返回的指针指向一个有一个int形参且返回int的函数;
很多C语言教材也会为各种声明式列举各种规则,比如:const位于星号左右。这些规则一多,过得时间一长,就很容易混淆,我以前就经常忘记这些规则。那么有没有一个更好的方法来对付这类“C语言声明式”呢?
答案是:有的!那就是“像C编译器一样去解析”。
事实上,不论多复杂多绕的C代码,都需要经过C编译器翻译成机器码(二进制码),而机器所能执行的都是简单的机器指令,如:加减、取值、赋值等。从这个角度看,C编译器对C代码的编译就是将单条复杂的C语言转为多条简单的机器指令的过程,因此,我们只需要循着编译器设计者的思路去解析“C语言声明式”,就可以百分百正确的理解它。
《Expert C Programming》第3章介绍了这种C编译器理解“C语言声明式”的方法,并设计了一个简单的“解析程序”。我们只需要掌握这种方法,就可以一劳永逸地解决所有“C语言声明式”类型的面试题。
一、优先级规则
我们可以尝试用通俗的语言把“C声明式”分解开来,按优先级规则,分别解释各个组成部分。C语言声明优先级规则如下:
A 声明从它的名字(变量名或函数名,函数指针名)开始读取,然后按照优先级顺序依次读取。
B 优先级从高到低依次是:
B. 1 声明中被括号括起来的那部分
B. 2 后缀操作符:
括号()表示这是一个函数,而
方括号[]表示这是一个数组
B. 3 前缀操作符:星号*表示“指向...的指针”
C 如果const和(或)volatile关键字的后面紧跟类型说明符(如int,long等),那么它作用于类型说明符。其他情况 下,const和(或)volatile关键字作用于它左边紧邻的指针星号*。
用优先级规则分析C语言声明一列:
char * const *(*next)();
解析过程如下表:
综上所述,这个声明式表示“next是一个指针,它指向一个函数,该函数返回另一个指针,该指针指向一个类型为char的常量指针”。
为了简化解析过程,我们需要逐步把已经处理过的片段“去掉”,化繁为简。再来一例:
char * (* c[10])(int **p);
它是一个有10个元素的数组,每一个元素是一个指针,该指针是函数指针,该函数有一个形参,其形参是一个指向int型的指针的指针,此外,该函数的返回值也是一个指针,返回值指针指向一个char字符。
注:我们在实际编程中,可以用typedef来简化复杂的C语言声明式。此外,在C++11中,也可以用using声明来替代typedef。
二、试题回顾
现在再回头来看,题1就比较简单了,它仅仅用到了规则C,我就直接给出答案啦
题1:
1)const char * p;
2)char const * p;
3)char * const p;
4)const char * const p;
1.1 p是一个指针,指向常量字符。指针指向的内存(区域)不可修改。
1.2 p是一个指针,指向常量字符。它与1.1相同,const与char的位置无关。
1.3 p是一个常量,p是一个常量指针,指向一个char字符。指针的值不可修改,即p内保持的地址值不可修改。
1.4 p是一个常量,p是一个常量指针,指向一个常量字符。指针的值和指针指向的内存都不可修改。
逐条分析题2,如下:
1)一个有10个指针的数组tmp,其指针指向整形数;
int * tmp[10]
2)一个指向有10个整形数数组的指针tmp;
int (* tmp)[10]
3)一个指向函数的指针,该函数有一个整形参数并返回一个整形数;
int (*)(int)
4)一个有10个指针的数组,其指针指向一种函数,该型函数有一个int*参数并返回int;
int (*)[10](int*)
5)一个指向有两个int形参并且返回一个函数指针的函数指针,返回的指针指向一个有一个int形参且返回int的函数;
int (*(*F)(int, int))(int)
注:2.1和2.2是一个非常经典的对比,需要牢记;而2.5则演示了括号嵌套关系,非常新颖。
三、自制解析程序
我们根据C编译器的原理,制作一个简易的“C语言声明式”解析程序——“cdecl”。
// cdecl.c#include执行效果:#include #include #include #define MAXTOKENS 100#define MAXTOKENLEN 64enum type_tag {IDENTIFIER,QUALIFIER,TYPE};struct token { char type; char string[MAXTOKENLEN];};int top = -1;struct token stack[MAXTOKENS];struct token this;#define pop stack[top--]#define push(s) stack[++top] = senum type_tag classify_string(void){ char * s = this.string; if (!strcmp(s, "const")) { strcpy(s, "read_only"); return QUALIFIER; } if (!strcmp(s, "volatile")) return QUALIFIER; if (!strcmp(s, "void")) return TYPE; if (!strcmp(s, "char")) return TYPE; if (!strcmp(s, "signed")) return TYPE; if (!strcmp(s, "unsigned")) return TYPE; if (!strcmp(s, "short")) return TYPE; if (!strcmp(s, "int")) return TYPE; if (!strcmp(s, "long")) return TYPE; if (!strcmp(s, "float")) return TYPE; if (!strcmp(s, "double")) return TYPE; if (!strcmp(s, "struct")) return TYPE; if (!strcmp(s, "union")) return TYPE; if (!strcmp(s, "enum")) return TYPE; return IDENTIFIER;}void gettoken(void){ char * p = this.string; while ((*p = getchar()) == ' '); if (isalnum(*p)) { while (isalnum(*++p = getchar())); ungetc(*p, stdin); *p = '\0'; this.type = classify_string(); return; } if (*p == '*') { strcpy(this.string, "pointer to"); this.type = '*'; return; } this.string[1] = '\0'; this.type = *p; return;}read_to_first_identifier() { gettoken(); while (this.type != IDENTIFIER) { push(this); gettoken(); } printf("%s is ", this.string); gettoken();}deal_with_arrays() { while (this.type == '[') { printf("array "); gettoken(); if (isdigit(this.string[0])) { printf("0..%d ", atoi(this.string) - 1); gettoken(); } gettoken(); printf("of "); }}deal_with_function_args() { while (this.type != ')') { gettoken(); } gettoken(); printf("function returning ");}deal_with_pointers() { while (stack[top].type == '*') { printf("%s ", pop.string); }}deal_with_declarator() { switch (this.type) { case '[': deal_with_arrays(); break; case '(': deal_with_function_args(); break; } deal_with_pointers(); while (top >= 0) { if (stack[top].type == '(') { pop; gettoken(); deal_with_declarator(); } else { printf("%s ", pop.string); } }}int main(){ read_to_first_identifier(); deal_with_declarator(); printf("\n"); return 0;}
使用该程序可以验证自己的判断是否正确。