[C专家]分析C语言声明——只有编译器才会喜欢的语法

Kernighan和Ritchie承认,“C语言声明的语法有时会带来严重的问题。”(K&R,第二版,第122页)。C语言声明的语法对于编译器(或编译器设计者)的处理来说并不是什么大不了的事。但对于一般的程序员,它却会成为障碍。语言的设计者也是人,他们也会犯错误。例如,Ada的语言参考手册在最后的附录中所附的Ada语法手册,有一处存在歧义。对于编程语言的语法来说,歧义是非常忌讳的,因为它使编译器设计者的工作严重复杂化。但C语言的声明语法确实非常可怕,渗透于整个语言使用的方方面面。毫不夸张地说,正是由于在组合类型方面的笨拙行为,C语言被显著且毫无必要地复杂化了。

C语言的声明模型之所以如此晦涩,这里有几个原因。六十年代晚期,人们在设计C语言的这部分内容时,“类型模型(type model)”这个概念对于当时的编程语言理论而言尚属陌生。BCPL(C语言的祖先)几乎没有类型,它把二进制字作为唯一的数据类型,所以C语言先天有缺。然后出现了一种C语言设计哲学,要求对象的声明形式与它的使用形式尽可能相似。一个int类型的指针数组被声明为 int *p[3],并以*p[i]这样的表达式引用或使用指针所指向的int数据,所以它的声明形式和使用形式非常相似。这种做法的好处是各种不同操作符的优先级在“声明”和“使用”时是一样的。它的缺点在于操作符的优先级是C语言中另一个设计不当、过于复杂之处。程序员需要记住特殊的规则才能推断出int *p[3]到底是一个int类型的指针数组,还是一个指向int数组的指针。

“声明的形式和使用的形式相似”这种用法可能是C语言的独创,其他语言并没有采用这种方法。而且,“声明的形式和使用的形式相似”即使在当时也不像是一个特别好的主意。把两种截然不同的东西做成同一个样子真的有什么重要的意义吗?贝尔实验室的学究们也承认此批评有理,但他们坚决死扛原来的决定,至今仍然。一个比较好的声明指针的方法是:

int &p;

它至少能提示p是一个整型数的地址。这种语法现在已被C++采纳,用于表示参数的传址调用。

C语言的声明所存在的最大问题是,你无法以一种人们所习惯的自然方式从左向右阅读一个声明。在ANSI C引入volatile和const关键字后,情况就更糟糕了。由于这些关键字只能出现在声明中(而不是使用中),这就使得现今声明形式和使用形式能完全对得上号的例子越来越少了。那些从风格上看像是声明,但却没有标识符的东西(如形式参数声明和强制类型转换)看上去显得滑稽。如果想要把什么东西的类型强制转换为指向数组的指针,就不得不使用下面的语句来表示这个强制类型转换:

char (*j)[20];/* j是一个指向数组的指针,数组内有20个char元素 */

j = (char (*)[20]) malloc(20);

如果把星号两边看上去明显多于的括号去掉,代码会变成非法的。

涉及指针和const的声明会出现几种不同的顺序:

const int * grape;

int const * grape;

int * const grape_jelly;

在最后一种情况下,指针是只读的。而在另外两种情况下,指针所指向的对象是只读的。

当然对象和指针有可能都是只读的,下面两种声明方法都能做到这一点:

contst int * const grape;

int const * const grape;

ANSI C提到typedef说明符之所以被称为“存储类型说明符”只是为了语法上的方便而已,它也不否认其中存在一些另外的问题。即使是经验丰富的C程序员也都觉得这里麻烦多多。如果像“指向数组的指针”这样概念清晰的语法,它的声明形式也是如此晦涩难懂,那么对于更复杂的语法形式又将如何。例如,你明白下面的声明(取自telnet程序)的确切意思吗?

char * const *(*next)();

我将在后面把这个声明作为实例讨论时再给出它的答案。多年来,程序员、学生和教师们都在努力寻找一种更好的记忆方法和规则来搞清楚恐怖的C语言声明语法。后面提供了一种方法,它采用一种循序渐进的方式来解决这个问题。用它操纵几个实例后,就不再会被C语言声明所困扰了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值