人们常说,c语言的声明太复杂了,的确,这也是c语言饱受批评的地方之一。不过,笔者认为,真正要受到批评的不是语言本身,而是那些传播者。传播者们通常都有一个共识:
讲述要由浅入深。作为原则,笔者并非要反对它,毕竟笔者对c语言的学习,也经历了相同的过程。但是,由浅入深并不意味着一切从简,以偏盖全。
计算机语言不同于数学理论(虽然它的确根植于数学,与数学密不可分),数学理论是一种循序渐进的过程,后面的理论以前面的理论为基础。但c语言归根说底,就是一堆语言规则而已,应该让学习者一开始就全面且详细地了解它,而不是象现在某些教材所做的那样,只说一部分,不说另一部分,以为这就是由浅入深了,实际上这是以偏盖全。
语言如此,声明作为c语言的一部分更是如此。我们最常见到的对声明的描述是这样的:
存储类别类型限定词类型标识符。
这种说明会给人们一种暗示:c语言的声明是静止的、死板的,什么声明都能够以这个为基础,往上一套就ok了。事实真的如此吗?
说句心里话,笔者也祈祷事实真的如此,这样世界就简单多了、清静多了。但别忘了,这个世界总是让人事与愿违的。实际上,c的声明的组织形式是以嵌套为基础的,是用嵌套声明组织起来的,并非象上面所述那么死板,存储类说明符一定得放在限定词前面吗?
类型说明符一定要紧贴标识符吗?不!c标准从来没有这样说过!
下面来看一看c89对声明的形式是如何规定的:
声明:声明说明符初始化声明符表opt [opt的意思是option,可选]
其中声明说明符由以下三项构成:
声明说明符:
存储类说明符声明说明符opt
类型说明符声明说明符opt
类型限定符声明说明符opt
在这里,一个声明说明符可以包含另一个声明说明符,这就是声明的嵌套,这种嵌套贯穿于整个声明之中,今天我们看来一个非常简单的声明,其实就是由多个声明嵌套组成的,例如:
static const int i=10, j=20, k=30;
变量i前面就是声明说明符部分,有三个声明说明符:static const int,static是一个存储类说明符,它属于这种形式:
static 声明说明符。
static后面的声明说明符就是const int,const是一个类型限定符,这也是个嵌套,它是由。
const 声明说明符。
组成,最后的int是一个类型说明符,到这里已经没有嵌套了,int就是最底的一层。对于存储类说明符、类型说明符和类型限定符的排列顺序,c标准并没有规定其顺序,谁嵌套谁都可以。换言之,上面的声明可以写成:
int static const i=10, j=20, k=30;或者const int static i=10, j=20, k=30;
这无所谓,跟原声明是一样的。再举一个有趣的例子:
const int *p;与int const *p;
有些人会对后面一种形式感到困惑,因为他一直以来学习的都是那种死板的形式,因此他无法理解为什么那个const可以放在int的后面。实际上对于标准来说,这是再正常不过的行为了。
上面举的例子是变量的声明,函数的声明也同样道理,例如:
static const int func(void);
int main(void)
int static const (*p)(void);
p=func;
return 0;
const int static func(void)
return 0;
func的函数原型声明、函数定义跟main内的函数指针p的声明是一样的。但是,笔者并非鼓励大家把声明说明符写得乱七八糟,作为一个良好的风格,应该按照已经习惯约定的方式排列说明符,但懂得其中的原理非常重要。
声明static const int i=10, j=20, k=30;的int后面的部分就是初始化声明符表,这比较容易理解,这个符表实际上也是嵌套的:
初始化声明符表:
初始化声明符。
初始化声明符表, 初始化声明符。
初始化声明符:
声明符。声明符=初值。
声明符是初始化声明符的主体,现在来讨论一下声明符是如何规定的:
声明符:指针opt 直接声明符。
这里写的指针opt指的是那个指针声明符*,要注意的是,*属于声明符,而不是声明说明符的一部分。
指针opt又包含:
指针: 类型限定符表opt
类型限定符表opt 指针。
在这里有一个常见的问题,就是const int *p;与int * const p的区别,第一个声明的const属于声明说明符,它跟int一起,是用来说明*p这个声明符的,因此const修饰的是p所指向的那个对象,这个对象是const的。而第二个声明的const是声明符的一部分,它修饰的对象是p本身,因此p是const的。
上面规定的第二条值得注意,这条规定产生了一种指针与const的复杂形式,例如:
const int * const **const **const p;(是不是有种想冲向厕所的冲动?)这是一种复杂的声明嵌套,如何解读这种声明?其实只要掌握了它的规律,无论它有多少个const、多少个*都不难解读的,这个内容我将在第九章进行解释。
剩下的就是直接声明符和类型限定词表的内容:
直接声明符:
标识符。声明符)
直接声明符[常量表达式opt]
直接声明符(形式参数类型表)
直接声明符(标识符表opt)
类型限定符表:
类型限定符。
类型限定符表类型限定符。
这一章的最后一个内容,是讨论一下typedef,typedef用来声明一个别名,typedef后面的语法,是一个声明。本来笔者以为这里不会产生什么误解的,但结果却出乎意料,产生误解的人不在少数。罪魁祸首又是那些害人的教材。
在这些教材中介绍typedef的时候通常会写出如下形式:
typedef int para;
这种形式跟#define int para几乎一样,如前面几章所述,这些教材的宗旨是由浅入深,但实际做出来的行为却是以偏盖全。的确,这种形式在所有形式中是最简单的,但却没有对typedef进一步解释,使得不少人用#define的思维来看待typedef,把int与para分开来看,int是一部分,para是另一部分,但实际上根本就不是这么一回事。int与para是一个整体!
就象int i:声明一样是一个整体声明,只不过int i定义了一个变量,而typedef定义了一个别名。这些人由于持有这种错误的观念,就会无法理解如下一些声明:
typedef int a[10];
typedef void (*p)(void);
他们会以为a[10]是int的别名,(*p)(void)是void的别名,但这样的别名看起来又似乎不是合法的名字,于是陷入困惑之中。实际上,上面的语句把a声明为具有10个int元素的数组的类型别名,p是一种函数指针的类型别名。
虽然在功能上,typedef可以看作一个跟int para分离的动作,但语法上typedef属于存储类声明说明符,因此严格来说,typedef int para整个是一个完整的声明。
C语言printf函数详解
0n n 1,2,3.宽度至少为n位,不够左边以0填充 格式列表中,下一个参数还是width prec 用于控制小数点后面的位数。无按缺省精度显示 0 当type d,i,o,u,x时,没有影响 当type e,e,f时,不显示小数点。n n 1,2,3.当type e,e,f时,表示的最大小数位数...
C语言 第二章 作业详解
题2.1 标识符在命名时,应注意哪几个方面?在c语言中标识符是用户自定义的一种字符序列,它由字母或下划线开头,由下划线 数字与字母组成的一组符号。在c语言中大小写有区别,一般情况下标识符尽可能 见文识意 它不能与系统的保留字相同。在c语言的不同编译环境中对于字符串的长度要求也不同,dos环境下,长度...
高级语言程序设计 C语言 习题答案及详解
1.1 单项选择题。1.二进制语言是属于 面向机器语言 面向问题语言 面向过程语言 面向汇编语言。解 人们研制了许许多多计算机程序设计语言,其中二进制语言直接来自计算机的指令系统,与具体计算机紧密相关,所以是一种面向机器语言。面向问题语言是为了易于描述和求解某类特定领域的问题而专门设计的一种非过程语...