分类: C/C++
2009-01-08 14:51:55
1.6 声明与定义
在C语言里准确地理解声明与定义的区别是十分重要的.
一个声明(declaration)为编译程序介绍标识符.其本质是:这个标识符是一个xxx且它的定义将在下面出现.一个声明的例子:
extern double sqrt(double);
在这个声明里,向编译程序介绍标识符sqrt,并告诉编译程序它是一个接受一个双精度浮点型数据参数及返回一个双精度浮点型数据结果的函数.仅此而已.没有为这个声明分配存贮空间,包括在编译程序内部的表.[31]
一个定义(definition)告诉编译程序为标识符分配存贮空间.例如,当定义一个上面提及的函数,则用于存贮编译程序产生的代码的空间将被建立,且在程序符号表里产生一个入口.同样的,当我们写下:
double balance;
就算编译程序从未见过这个标识符,在这个定义之后将知道这是一个双精度浮点型数据.[32]
1.6.1 变量的声明
一个变量如下声明:
<类型> <标识符> ;
如:
int a;
double d;
long long h;
所有的这些都是定义变量.如果因为这些变量在别处被定义,你在这里只想声明一个变量,而不为它分配任何存贮空间,你可以加上关键字extern:
extern int a;
extern double d;
extern long long d;
随意地,你可以定义一个标识符,并给赋予它某个计算的结果作为它的值:
double fn(double f) {
double d = sqrt(f);
// 更多的语句…
}
注意:只能在函数里将变量初始化为一个在编译时不确定的值.在函数的外面你可以这样写:
int a = 7;
或
int a = (1024*1024)/16;
但你所赋的值在编译时必须是不变的,即:当使用它时编译程序可以将值描述出来.
指针使用一个星号声明:
int *a;
你可以在同一个声明语句里声明几个相同类型的标识符,如下:
int a,b=7,*c,h;
注意:c是一个指向一个整型数据的指针,因为它有一个星号在其左边.这个符号有点让人迷惑,常常会忘记星号.当所有要声明的标识符是同一种数据类型可以使用这种多重声明并把指针放在独立的一行.
C声明的语法被批评是十分模糊的.这是个事实;且没有办法拒绝这个明显的弱点.在Peter van der Linden的《Deep C secrets》[34]这本书里写了一个简单的口诀来记住这些语法.口诀如下(在此书的第三章):
The Precedence Rule for Understanding C Declarations.
-------------------------------------------------------------------
规则1:从名字开始读声明,然后按优先级读.
规则2:优先级从高到低是:
2.A:声明中()里的部分
2.B:后缀操作符
2.B.1:圆括号()标识一个函数原型,
2.B.2:括号[]标识一个数组,
2.B.3:前缀操作符:星号*表示“指向…的指针”.
规则3:如果const和(或)volatile关键字的后面紧跟类型说明符(如int,long等),
那么它作用于类型说明符。在其他情况下,const和(或)volatile关键字作用
于它左边紧邻的指针星号。
-------------------------------------------------------------------
原文:
Rule 1: Declarations are read by starting with the name and then reading in precedence order.
Rule 2: The precedence, from high to low, is:
2.A : Parentheses grouping together parts of a declaration
2.B: The postfix operators:
2.B.1: Parentheses ( ) indicating a function prototype, and
2.B.2: Square brackets [ ] indicating an array.
2.B.3: The prefix operator: the asterisk denoting "pointer to".
Rule 3: If a const and/or volatile keyword is next to a type specifier e.g. int, long, etc.) it
applies to the type specifier. Otherwise the const and/or volatile keyword applies to the pointer
asterisk on its immediate left.
使用这条口诀我们甚至可以理解这个声明:
char * const *(*next)(int a, int b);
我们从变量名开始,这个例子是“next”.这是被声明的名称.我们看在圆括号里的表达式有一个星号,因此我们可以断定“next是一个指向…的指针”.
又看圆括号外面有一个星号在左边,和一个函数原型在右.使用规则2.B.1我们以原型继续.
“next是一个指向有两个参数的函数的指针”.再看星号:"next是一个指向有两个参数的返回一个指向…指针的函数的指针",最后我们加上char
*const,就是:"next是一个指向有两个参数的返回一个指向一个指向一个字符型常量的指针的指针的函数的指针"或next是一个指向有两个参数的
返回一个指向一个常量指针的函数的指针,常数指针指向一个字符型常量".(译者:哪一种容易理解?都不太好理解,读者你试试看!英文是这样的:
"next" is a pointer
to a function with two arguments returning a pointer to a constant pointer to char.).
下面看这个例子:
char (*j)[20];
Again,
从“j是一个指向…的指针”开始.在右边是一个在方括号的表达式,因此适用2.B.2得到“j是一个指向一个有20个元素的数组的指针”.再加上
“char”是: “j是一个指向一个有20个字符型元素的数组的指针”.注意:当产生一个映射时,没有标识符也可使用同样形式的声明:
j = (char (*)[20]) malloc(sizeof(*j));
我们看在圆括号里的粗体字(一个映射)同样是一个声明,只是没有标识符j.
1.6.2 函数的声明
函数的声明的详细说明:
? 函数返回值的数据类型
? 名称
? 每个参数的数据类型
通常的形式是:
<类型> <名称>(<参数的数据类型 1>, … <参数的数据类型 N> ) ;
double sqrt(double) ;
注意 :一个标识符可以加到声明里,但它是可要可不要的.我们可以这样写 :
double sqrt(double x);
只要你喜欢,但“x”不是所要求的将被编译程序忽略.
函数的参数的数量是可变的.函数“printf”就是一个取得多个参数的例子.这些函数如此声明:
int printf(char *, ...);
省略符号意为“更多的参数”.[35]
为何函数的声明如此重要?
当我们在C里开始编程时,函数的原型是不存在的.因此你可以如下定义一个函数:
int fn(int a)
{
return a+8;
}
而在另一个模块里这样写:
fn(7,9);
没有任何问题.
Well,在编译时当然没有问题.只是程序运行时崩溃或返回一个乱七八糟的结果.如果你编写一个由很多模块很多人参与的大系统,
这种错误存在的可能性几乎为100%.避免这种错误的发生是十分重要的.你可以在大部分的时间里避免,可你不可能完全地避免这种错误的发生.
函数原型提出编译时检查所有的函数调用.因此我们不用担心在程序调试时花费大量的时间去排除这样的错误.在C++语言里,如果遇到一个没有原型的函数编译程序会中止编译.我曾经多次想把这种机制引入到Lcc-win32里,因为忽略函数原型常常导致错误,但出于兼容性的原因,我没有这样做.[36]
1.6.3 函数的定义
函数的定义看起来和函数的声明很相像,不同之处是用一个在花括号里的语句块代代替了分号,就像我们在上面看到的main函数.另一个不同点就是我们要给每个参数指定名称,这些标识符不再是可选项: 需要在函数体内使用它们.下面是一个微不足道的例子:
int addOne(int input)
{
return input+1;
}
1.6.4 变量的定义
一个变量的定义将告诉编译程序为其分配内存空间.例如当编译程序见到如下语句将为变量分配内存空间:
int a;
或:
int a = 67;
在第一个例子,编译程序在程序没有初始化的变量段里为变量分配sizeof(int)个字节的内存空间.而第二个例子则在已初始化的变量段里为赋值为67的变量分配同样的内存空间.
1.6.5 语句的语法
在C语言里,那些如if或while之类的封闭的控制语句表达式,必须在一个圆括号里.在很多语言是没有必要的.如这样写:
if a < b run(); // 在C里不能这样写...
在C语言里,if语句需要一个圆括号:
if (a
在C语言里, 赋值是一个表达式,即:它可以出现在一个很复杂的表达式里:
if ( (x =z) > 13) z = 0;
其意义是在编译程序里产生将z的值赋予x的代码,再将x的值与13比较,如果关系成立,则程序将z的值设为0.
[31] 注意:如果一个函数只是被定义而没有使用,将完全没有为此函数分配存贮空间.在一个已编译的程序里没有使用任何空间,除非被有效地使用.若是被有效使用,编译程序将发出一条记录告诉连接程序这个对象在某处被定义.