在 C 中,谈到变量范围(scope)涉及许多层次,可以谈到很复杂,这边先谈谈全局变量(Global variable)、局部变量(Local variable)与区块变量(Block variable)。
全局变量是指直接定义在(主)函数之外的变量,这个变量在整个程序之中都可见,例如:
const double PI = 3.14159;
doule area(double r) {
return r * r * PI;
}
int main(void) {
// .....
return 0;
}
在这个例子中,PI
这个变量可以在主函数main
与函数area
使用,全局变量最好只用来定义一些常数,或者是确实具有全局概念的变量,不应为了方便而草率地将变量设为全局变量,否则会发生命名空间重叠等问题;全局变量的生命周期始于程序开始,终止于程序结束。
局部变量是指函数中定义的变量,或是定义在参数列的参数,范围只在函数之内,例如在上例的main
函数无法取用area
函数的变量r
,局部变量的生命周期始于函数执行,终止于函数执行完毕。
区块变量是指定义在某陈述区块中的变量,例如while
循环区块,或是for
循环区块,例如下面的变量i
在循环结束之后,就不再有效:
for(int i = 0; i < 100; i++) {
// ....
}
范围大的变量与范围小的变量同名状况时,范围小的变量会暂时覆盖(shadow)范围大的变量,称为变量覆盖,例如:
int i = 10;
for(int i = 0; i < 100; i++) {
// ...
}
printf("%d\n", i);
这个程序最后显示的i
值是 10,执行循环时,循环中的i
变量范围覆盖循环外的i
变量;全局变量与局部变量同名时也是如此运作。
再来介绍static
,这个关键字有两种不同的概念,内存模式与连结的方式,依使用的场合而有所不同。
就内存模式而言,变量定义时若加上static
,执行时期会一直存在内存的固定位置,在不同 .c 文件顶层定义的变量,即使没有加上static
,也是这种内存模式。
因此若在函数中定义static
变量,代表着就算函数执行完毕,变量也不会消失。例如:
#include <stdio.h>
void count();
int main(void) {
for(int i = 0; i < 10; i++) {
count();
}
return 0;
}
void count() {
static int c = 1;
printf("%d\n", c);
c++;
}
执行结果:
1
2
3
4
5
6
7
8
9
10
虽然变量c
是在count
函数定义,但是函数结束后,变量仍然有效,直到程序执行结束时才消失,虽然变量一直存在,但由于范围限于函数之中,函数外仍无法访问该变量。
就连结模式而言,得先来探讨一下,定义在另一个 .c 文件顶层范围中的变量,可以直接拿来用吗?默认是不可以的,然而,可以透过extern
声明变量会在其他地方定义,例如:
foo.c
double v = 1000;
// ... 其他定义
main.c
#include <stdio.h>
int main() {
extern double v;
printf("%f\n", v);
return 0;
}
在 main.c 中并没有定义v
,只是以extern
声明v
是在其他地方定义,编译器会试着找出符合的v
,结果在 foo.c 找到,因而会显示结果为 1000,要注意的是,extern
声明v
在其他位置定义,因此不能与初始化化使用。
#include <stdio.h>
int main() {
extern double v = 2000; // error, `v' has both `extern' and initializer
// ...
return 0;
}
若要设定v
变量,必须在extern
声明之后:
#include <stdio.h>
int main() {
extern double v;
v = 2000;
// ...
return 0;
}
定义在 .c 文件的函数,若非实现标头文件中定义的函数原型,默认是不能在另一个 .c 中使用的,若要使用得用extern
定义,例如,若 a.c 中定义了void foo() {...}
,main.c 要使用,必须用extern
如下定义:
extern void foo();
定义在一个 .c 中的名称,可以只改为内部连结,也就是想表示它只用在该 .c 中,这时可以加上static
,例如,若方才的 foo.c 中的v
定义为:
foo.c
static double v = 1000;
// ... 其他定义
那么范畴就只局限在 foo.c 中了,不会被extern
拿来连结。
函数若使用static
修饰,表示内部连结,不会被extern
拿来连结,如果想将函数实现定义在 .h 文件中,可以加上static
修饰。例如:
// 定义在 .h 中
static void foo() {
...
}