C语言中变量的作用域

181 阅读3分钟

昨天刚总结完JavaScript中的var``letconst的区别, 今天翻开《C和指针》正好翻到作用域这一节, 就自己总结例子实践一下.

代码块作用域

和JavaScript类似(其实这句话应该反过来说, 不过无所谓了), C语言中默认声明的标识符其实是类似于var的行为的.

以同一个文件内的标识符为例.

#include <stdio.h>
#define var_name(val) #val

int a = 1;
int b (int c);

int d (int e)
{
    int f = 10;
    printf("prints in function d | %s@%p: %d\n", var_name(f), &f, f);
    int g (int h);

    if (1) {
        int f, g, i;
        f = 20;
        printf("prints in function d | %s@%p: %d\n", var_name(f), &f, f);
        g = 30;
        printf("prints in function d | %s@%p: %d\n", var_name(g), &g, g);
        i = 40;
        printf("prints in function d | %s@%p: %d\n", var_name(i), &i, i);
    }

    if (2) {
        int i;
        i = 50;
        printf("prints in function d | %s@%p: %d\n", var_name(i), &i, i);
    }

    printf("prints in function d | %s@%p: %d\n", var_name(f), &f, f);

    printf("prints in function d | %s@%p: %d\n", var_name(a), &a, a);

    return 0;
}


int main()
{
    printf("prints in function main | %s@%p: %d\n", var_name(a), &a, a);
    a = 2;
    printf("prints in function main | %s@%p: %d\n", var_name(a), &a, a);
    int a = 3;
    printf("prints in function main | %s@%p: %d\n", var_name(a), &a, a);
    a = 4;
    printf("prints in function main | %s@%p: %d\n", var_name(a), &a, a);

    int e = 10;
    d(e);
}

结果如图:

image-20180914235911750

从上面的实验结果可以得出以下结论:

  1. 代码块外部声明的标识符是在文件内是全局的.

    对于a而言, 在main函数内被重新赋值, 然后在main代码块内定义了一个新的标识符a, 但其实这两个标识符除了名字相同之外, 并不是同一个标识符. 这在d方法内打印的a的值可以看出. 在d方法执行之前, 全局的a已经被重新赋值成了2, 而后面被重新赋值的其实是局部标识符的a, 因此在d中打印的a是2.

  2. 行为和JavaScript的var一致, 问题也一样

    在main中首先重新赋值了a, 然后定义了一个同名的标识符, 在同一个代码块中出现了位于不同作用域的标识符, 没有报错也没有警告, 这在JavaScript中引入let来解决了这个问题, 但在C中, 估计这辈子是见不到解决方案了.

文件作用域

任何在所有代码块之外声明的标识符都具有文件作用域(file scope), 它表示这些标识符从它们的声明指出知道它所在的源文件的结尾处都是可以访问的. 上例中的标识符a就属于此类.

在文件中定义的函数名也具有文件作用域, 因为函数名本身并不属于任何代码块.

需要指出的是, 在头文件中编写通过#include指令包含到其他文件中的声明就好像是直接写在那些文件中一样, 它们的作用域并不局限于头文件的文件尾.

原型作用域

**原型作用域(prototype scope)**只适用于在函数原型中声明的参数名. 在原型中, 参数的名字并非必需. 但是, 如果出现参数名, 可以给它取任何名字, 不必与函数定义中的参数名匹配, 也不必与函数实际调用时所传递的实参匹配. 原型作用域防止这些参数名与程序其他部分的名字冲突.

函数作用域

goto有关, 忽略.