变量范围


在 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() {
    ...
}




展开阅读全文