作用域讲的是在哪些上下文中能看到哪些变量。
几乎所有开发者定义的变量都属于这种作用域。它包括其他语言可能称为“函数作用域”的东西,也就是在函数内部声明的变量。
基本规则是:如果你在一个由花括号界定的块里声明了变量,那么这个变量的作用域就是这个块。
如果块里还有块,那么在内层块中声明的变量只属于那个块,在外层作用域里是看不到的。
一旦变量的作用域结束,这个变量就不能再被引用了,你可以认为它的值已经掉进了天上那个伟大的位桶91里。
一个嵌套作用域的例子:
#include <stdio.h>
int main(void)
{
int a = 12; // 属于外层块,但在内层块中可见
if (a == 12) {
int b = 99; // 属于内层块,在外层块中不可见
printf("%d %d\n", a, b); // 可以:"12 99"
}
printf("%d\n", a); // 可以,我们仍在 a 的作用域里
printf("%d\n", b); // 非法,超出了 b 的作用域
}另一个有趣的事实是,只要合理,你可以在块中的任何位置定义变量——它们的作用域是整个块,但在定义之前不能使用。
#include <stdio.h>
int main(void)
{
int i = 0;
printf("%d\n", i); // 可以:"0"
//printf("%d\n", j); // 非法——j 在定义之前不能使用
int j = 5;
printf("%d %d\n", i, j); // 可以:"0 5"
}历史上,C 要求所有变量都在块中任何代码之前定义,但在 C99 标准中这已经不再是必须的了。
如果内层作用域里有个变量与外层作用域里的某个变量同名,那么只要你在内层作用域中运行,内层的那个就优先。也就是说,在它的生命周期内,它遮蔽了外层作用域里的那个变量。
#include <stdio.h>
int main(void)
{
int i = 10;
{
int i = 20;
printf("%d\n", i); // 内层作用域的 i,20(外层的 i 被遮蔽了)
}
printf("%d\n", i); // 外层作用域的 i,10
}你可能已经注意到,在那个例子里我在第 7 行随手扔了一个块进去,连个用来引出它的 for 或 if 语句都没有!这完全合法。有时开发者会为了做一次快速计算而把一堆局部变量归到一起,就会这么做,不过很少见。
如果你在块外定义变量,这个变量就具有文件作用域。它在文件中位于其后的所有函数里都可见,并在这些函数之间共享。(例外情况是:某个块如果定义了同名变量,就会遮蔽文件作用域里的那个。)
这最接近你在其他语言里所认为的“全局”作用域。
例如:
#include <stdio.h>
int shared = 10; // 文件作用域!在此之后对整个文件可见!
void func1(void)
{
shared += 100; // 现在 shared 的值为 110
}
void func2(void)
{
printf("%d\n", shared); // 打印 "110"
}
int main(void)
{
func1();
func2();
}注意,如果 shared 声明在文件末尾,就无法通过编译。它必须在任何使用它的函数之前声明。
还有一些办法可以进一步修饰文件作用域里的项,也就是用 static 和 extern,不过这些我们稍后再细说。
for 循环作用域 我真不知道这该叫什么,因为 C11 §6.8.5.3¶1 并没有给它一个正式的名字。本书里我们也已经用过好几次了。它指的是在 for 循环的第一个子句里声明变量:
for (int i = 0; i < 10; i++)
printf("%d\n", i);
printf("%d\n", i); // 非法——i 的作用域只在这个 for 循环里在那个例子里,i 的生命周期从它被定义的那一刻开始,一直持续到循环结束。
如果循环体被包在一个块里,那么在 for 循环中定义的变量从那个内层作用域里是可见的。
当然,除非那个内层作用域把它们遮蔽了。这个疯狂的例子会打印五次 999:
#include <stdio.h>
int main(void)
{
for (int i = 0; i < 5; i++) {
int i = 999; // 遮蔽了 for 循环作用域里的 i
printf("%d\n", i);
}
}C 规范确实提到了函数作用域,但它只用于标签,这个我们还没讨论过。改天再聊。