作用域是指程序中定义的变量(或其他标识)符的可见性和生命周期范围,决定了变量(或其他标识)可以在哪些位置被使用。
在 C 语言中,任意一对花括号({})就是一个独立的作用域,在作用域中定义的变量称为该作用域的 局部变量(Local Variables)。
变量在定义时占用相应的内存空间,在离开作用域时其占用的内存被释放。
典型的作用域环境就是函数。例如:
void foo()
{
int x = 10; // x 是函数 foo 内的局部变量
int y = 20; // y 是函数 foo 内的局部变量
}
void bar()
{
int x = 6; // x 是函数 bar 内的局部变量
}
函数 foo 中定义了两个局部变量 x 和 y,函数 bar 中不能访问这两个变量
函数 bar 中中定了局部变量 x,虽然和函数 foo 中的变量 x 同名,但它们毫无关系,函数 foo 中不能访问这个变量
函数的形式参数也是该函数的局部变量。
条件语句、循环语句、分支语句等也是独立的作用域。例如:
int main()
{
// x 是函数 main 内的局部变量
int x = 1;
if (1)
{
// if 块作用域
// 作为 main 函数作用域的子作用域,可以访问 x
// y 是 if 块内的局部变量
int y = 2;
}
else
{
// else 块作用域
// 作为 main 函数作用域的子作用域,可以访问 x
// 不能访问 if 块作用域中的 y
// z 是 else 块内的局部变量
int z = 3;
}
while (0)
{
// while 块作用域
// 作为 main 函数作用域的子作用域,可以访问 x
// 不能访问 if 块作用域中的 y
// 不能访问 else 块作用域中的 z
// y 是 while 块内的局部变量,与前面 if 块中的 y 同名,但毫无关系
int y = 4;
}
for (int i = 0; i < 10; i+=1) // 这里定义的 i 也是 for 块作用域内的局部变量
{
// for 块作用域
// 作为 main 函数作用域的子作用域,可以访问 x
// 不能访问 if 块作用域中的 y
// 不能访问 else 块作用域中的 z
// 不能访问 while 块作用域中的 y
// z 是 for 块内的局部变量,与前面 else 块中的 z 同名,但毫无关系
int z = 5;
}
// 直接使用花括号创建块作用域
{
// 作为 main 函数作用域的子作用域,可以访问 x
// 不能访问 if 块作用域中的 y
// 不能访问 else 块作用域中的 z
// 不能访问 while 块作用域中的 y
// 在此之前访问的 x 是 main 函数前面定义的 x
// x 是块内的局部变量,与 main 函数前面定义的 x 同,但毫无关系
int x = 6; // 与外层变量同名,容易引起混淆,不建议使用
// 在此之后范围的 x 是此处新定义的 x
}
return 0;
}
flowchart
main --可访问--> x1["x"]
y1["y"] ~~~ if --可访问--> y1
z1["z"] ~~~ else --可访问--> z1
y2["y"] ~~~ while --可访问--> y2
z2["z"] ~~~ for --可访问--> z2
x3["x"] ~~~ block --可访问--> x3
i ~~~ for --可访问--> i
if["if 块"] --可访问--> main
else["else 块"] --可访问--> main
while["while 块"] --可访问--> main
block["无名块"] --可访问--> main
for["for 块"] --可访问--> main
函数 main 中定义了局部变量 x,函数内部的其它子作用域可以访问它
if 块中定义了局部变量 y,其它作用域不能访问它
else 块中定义了局部变量 z,其它作用域不能访问它
while 块中定义了局部变量 y,其它作用域不能访问它
此处的 y 与 if 中的 y 同名,但毫无关系
for 块中定义了局部变量 i 和 z,其它作用域不能访问它
此处的 z 与 else 中的 z 同名,但毫无关系
末尾处的块作用域中定义了局部变量 x,其它作用域不能访问它
此处的 x 与 main 函数前面定义的 x 同名,但毫无关系
注意,在末尾的块作用域中可以访问 main 函数前面定义的变量 x。因此:
在其内部定义 x 之前,访问的 x 是 main 函数前面定义的 x
在其内部定义 x 之后,访问的 x 是块内部新定义的 x
这种与外部变量同名的局部变量被称为影子(Shadow)变量,由于极易引起混淆,因此建议避免使用。
分支语句和上述其它块一样是独立的作用域,但其存在一些特别之处。
下面代码是错误的:
int x = 2;
switch (x)
{
case 1:
int n = 10; // n 是 switch 块内的局部变量
break;
case 2:
printf("%d\n", n); // 访问局部变量 n
break;
}
这段代码从作用域规则上来看没有问题:在 switch 块中定义了局部变量 n,然后在作用域内访问它。
但当 x 的值是 2 是,case 1 不会被执行,因此变量 n 不会被定义,也就无法被访问。
因此,语法规定 switch 块作用域中不能直接定变量;如果需要定义变量,则必须使用花括号({})创建新的块作用域。
例如:
switch (x)
{
case 1:
{
int n = 10; // n 是子块内的局部变量,其它作用域不能访问它
break;
}
case 2:
{
// 这里不能范围 case 1 中的 n,但可以重新定义一个
int n = 20; // n 是子块内的局部变量,其它作用域不能访问它
printf("%d\n", n); // 访问局部变量 n
break;
}
}
case 1 的块中定义了局部变量 n,在 case 2 中不能访问它
case 2 中另外定义了一个局部变量 n,它与 case 1 块中的 n 没有任何关系
在所有块之外的作用域被称为全局作用域,全局作用域中定义的变量称为 全局变量(Global Variables)。
例如:
#include <stdio.h>
// 定义全局变量
const double PI = 3.1415926;
/*********************************************
* @brief 计算圆的周长
* @param r 半径
* @return 圆的周长
********************************************/
double circle_circumference(double r)
{
return 2 * PI * r; // 访问全局变量 PI
}
/*********************************************
* @brief 计算圆的面积
* @param r 半径
* @return 圆的面积
********************************************/
double circle_area(double r)
{
return PI * r * r; // 访问全局变量 PI
}
int main(void)
{
double r = 10.0;
double circumference = circle_circumference(r);
double area = circle_area(r);
printf("半径为 %f 的圆,周长为 %f,面积为 %f\n", r, circumference, area);
return 0;
}
运行结果:
半径为 10.000000 的圆,周长为 62.831852,面积为 314.159260
这个示例中定义了全局变量 PI,在函数 circle_circumference 和 circle_area 均可访问该变量。