1.10 外部变量与作用域
main 函数中的变量(如 line、longest 等)是 main 函数的私有变量或局部变量
由于它们是在 main 函数中声明的,因此其它函数不能直接访问它们
其它函数中声明的变量也同样如此
例如,getline 函数中声明的变量 i 与 copy 函数中声明的变量 i 没有关系
函数中的每个局部变量只在函数被调用时存在,在函数执行完毕退出时消失
这也是其它语言通常把这类变量称为自动变量的原因
以后我们使用 “ 自动变量 ” 代表 “ 局部变量 ”
第 4 章将讨论 static 存储类,这种类型的局部变量在多次函数调用之间保持值不变
由于自动变量只在函数调用执行期间存在
因此,在函数的两次调用之间,自动变量不保留前次调用时的赋值
且在每次进入函数时都要显式为其赋值
如果自动变量没有赋值, 则其中存放的是无效值
除自动变量外,还可以定义位于所有函数外部的变量
也就是说,在所有函数中都可以通过变量名访问这种类型的变量
这种机制类似于 Fortran 语言中的 COMMON 变量,或 Pascal 语言中最外层程序块声明的变量
由于外部变量可以在全局范围内访问,因此,函数间可以通过外部变量交换数据,而不必使用参数表
外部变量一直存在,而不是在函数调用时产生、在函数执行完毕时消失
即使在对外部变量赋值的函数返回后,这些变量仍将保持原来的值不变
外部变量必须定义在所有函数之外,且只能定义一次,定义后编译程序将为它分配存储单元
在每个需要访问外部变量的函数中,必须声明相应的外部变量,此时说明其类型
声明时可以用 extern 语句显式声明,也可以通过上下文隐式声明
为了更详细地讨论外部变量,我们改写上述打印最长文本行的程序,把 line、longest 与 max 声明成外部变量
这需要修改这 3 个函数的调用、声明与函数体
#include <stdio.h>
#define MAXLINE 1000 /* maximum input line size */
int max; /* maximum length seen so far */
char line[MAXLINE]; /* current input line */
char longest[MAXLINE]; /* longest line saved here */
int getline(void);
void copy(void);
/* print longest input line; specialized version */
main()
{
int len;
extern int max;
extern char longest[];
max = 0;
while ((len = getline()) > 0)
if (len > max) {
max = len;
copy();
}
if (max > 0) /* there was a line */
printf("%s", longest);
return 0;
}
/* getline: specialized version */
int getline(void)
{
int c, i;
extern char line[];
for (i = 0; i < MAXLINE - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
line[i] = c;
if (c == '\n') {
line[i] = c;
++i;
}
line[i] = '\0';
return i;
}
/* copy: specialized version */
void copy(void)
{
int i;
extern char line[], longest[];
i = 0;
while ((longest[i] = line[i]) != '\0')
++i;
}
在该例子中,前几行定义了 main、getline 与 copy 函数使用的几个外部变量
声明了各外部变量的类型,这样编译程序将为它们分配存储单元
从语法角度看,外部变量的定义与局部变量的定义是相同的,但由于它们位于各函数的外部,因此这些变量是外部变量
函数在使用外部变量之前,必须要知道外部变量的名字
要达到该目的,一种方式是在函数中使用 extern 类型的声明
这种类型的声明除了在前面加了一个关键字 extern 外,其它方面与普通变量的声明相同
某些情况下可以省略 extern 声明
在源文件中,如果外部变量的定义出现在使用它的函数之前,那么在那个函数中就没有必要使用 extern 声明
因此,main、getline 及 copy 中的几个 extern 声明都是多余的
在通常的做法中,所有外部变量的定义都放在源文件的开始处,这样就可以省略 extern 声明
如果程序包含在多个源文件中,而某个变量在 file1 文件中定义、在 file2 和 file3 文件中使用
那么在文件 file2 与 file3 中就需要使用 extern 声明来建立该变量与其定义之间的联系
人们通常把变量和函数的 extern 声明放在一个单独的文件中(习惯上称之为头文件)
并在每个源文件的开头使用 #include 语句把所要用的头文件包含进来,后缀名 .h 约定为头文件名的扩展名
例如,标准库中的函数就是在类似于 <stdio.h> 的头文件中声明的
更详细的信息将在第 4 章中讨论,第 7 章及附录 B 将讨论函数库
在上述特别版本中,由于 getline 与 copy 函数都不带参数
因此从逻辑上讲,在源文件开始处它们的原型应该是 getline() 与 copy()
但为了与老版本的 C 语言程序兼容,ANSI C 语言把空参数表看成老版本 C 语言的声明方式,并且对参数表不再进行任何检查
在 ANSI C 中,如果要声明空参数表,则必须使用关键字 void 进行显式声明
第 4 章将对此进一步讨论
读者应该注意到,这节中我们在谈论外部变量时谨慎地使用了定义(define)与声明(declaration)这两个词
“ 定义 ” 表示创建变量或分配存储单元,而 “ 声明 ” 指的是说明变量的性质,但并不分配存储单元
顺便提一下,现在越来越多的人把用到的所有东西都作为外部变量使用
因为似乎这样可以简化数据的通信 —— 参数表变短了,且在需要时总可以访问这些变量
但是,即使在不使用外部变量的时候,它们也是存在的
过分依赖外部变量会导致一定的风险,因为它会使程序中的数据关系模糊不清
外部变量的值可能会被意外地或不经意地修改,而程序的修改又变得十分困难
我们前面编写的打印最长文本行的程序的第 2 个版本就不如第 1 个版本好,原因有两方面
其一便是使用了外部变量
另一方面,第 2 个版本中的函数将它们所操纵的变量名直接写入了函数,从而使这两个有用的函数失去了通用性
到目前为止,我们已经对 C 语言的传统核心部分进行了介绍
借助于这些少量的语言元素,我们已经能够编写出相当规模的有用的程序