博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一招搞定“C语言声明式”类型的面试题
阅读量:117 次
发布时间:2019-02-26

本文共 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;}
执行效果:

    使用该程序可以验证自己的判断是否正确。

你可能感兴趣的文章