一招搞定“C语言声明式”类型的面试题

    在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 <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#define MAXTOKENS 100
#define MAXTOKENLEN 64

enum 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] = s

enum 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;
}

执行效果:



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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值