块是一组语句和声明组合成的语法单元。 块通常(但并非总是)用大括号定义。 块可以嵌套在函数中,也可以嵌套在其他块中。
在下面的例子中,number定义在main函数里。
flag定义在嵌套于main函数中的if块里。
在块内定义的变量只在该块内有效。
因此,尝试编译这段代码会导致编译错误。
#include <stdio.h>
int main() {
int number = 1;
if (number == 1) {
int flag = 1;
printf("%d", flag);
}
// this line generates a compilation error
// error: 'flag' undeclared (first use in this function)
printf("%d", flag);
}
这条错误信息可能会让人困惑,因为flag在函数的前面确实被用到了。
但flag定义在函数内部的一个块里。
内层块能“看到”外层函数中的变量number,但外层函数看不到定义在其内层块中的flag变量。
一旦执行离开这个块,flag变量就会被自动销毁,程序再也无法使用它。
main函数本身是在最后一条printf语句里才第一次见到flag这个标识符的。
如果去掉最后那条printf语句,程序就能通过编译,运行时打印出1。
这是因为flag变量在包含第一条printf语句的那个块中是有效的。
从C99起,单行的if、for、while和do语句也被视为块。
在下面的例子中,虽然i没有定义在大括号里,但它只存在于for语句定义的块中,外层main函数看不到它。
#include <stdio.h>
int main() {
for (int i = 0, j = 11; i < j; i+=2)
printf("%d", i);
// this line generates a compilation error
// error: 'i' undeclared (first use in this function)
printf("%d", i);
}
如果去掉最后那条printf语句,程序就能通过编译,运行时打印出0 2 4 6 8 10 。
有些语言,例如 Go 和 Rust,明确规定单行的条件语句和循环语句也构成一个块。
它们要求这类单行语句仍然要用大括号括起来。
函数在某种程度上有点像块。 在函数中定义的变量,其标识符在函数外部看不到;正如在块中定义的变量,其标识符在块外部也看不到。 两个函数中的两个变量可以同名,因为这两个函数彼此看不到对方内部;同样,两个块中的两个变量也可以同名,因为这两个块彼此看不到对方内部。 但函数和块并不完全相同。 函数不能定义在另一个函数内部,而块却可以嵌套在另一个块里。 而且块中的某些标识符在块外也能被看到。 在下面的例子中,这段代码无法通过编译
#include <stdio.h>
int main() {
{
int i = 0;
begin:
printf("i == %d\n", i);
if (i == 0) {
i = 1;
goto begin;
}
}
{
int i = 1;
begin:
printf("i == %d\n", i);
if (i == 1) {
i = 0;
goto begin;
}
}
}
上面的代码编译失败,报出以下错误
/tmp/gH2TVYnTSJ.c: In function 'main':
/tmp/gH2TVYnTSJ.c:17:5: error: duplicate label 'begin'
17 | begin:
| ^~~~~
/tmp/gH2TVYnTSJ.c:7:5: note: previous definition of 'begin' was here
7 | begin:
| ^~~~~
虽然i可以在每个块中各自定义一次,但label名称在一个函数中必须唯一。
这是因为labels具有函数作用域:无论标签定义在函数中的哪个位置,整个函数都能看到它。
所以,与变量不同,label无论定义在函数中的哪里,它在同一个函数内都必须有一个唯一的名字。
要让代码能正常工作,需要修改其中一个labels的名称,使两者都唯一,如下例所示
#include <stdio.h>
int main() {
{
int i = 0;
begin:
// prints
// i == 0
// i == 1
printf("i == %d\n", i);
if (i == 0) {
i = 1;
goto begin;
}
}
{
int i = 1;
start:
// prints
// i == 1
// i == 0
printf("i == %d\n", i);
if (i == 1) {
i = 0;
goto start;
}
}
}
单行语句可以“堆叠”在一起,不用大括号也仍然处在同一个块中。
在下面的例子中,for语句里有一条if语句,而这条if语句里又有一条printf语句。
虽然for块里一共有两条语句,但只有那条单行的if语句会作为for循环的循环体执行。
而if语句里那条单行的printf语句,又会作为if语句的主体执行。
如果嵌套的每个块都只有一行,那么所有块都不需要大括号。
注意,注释不需要用大括号把它和if块里的printf语句括在一起,因为注释不是语句。
缩进让这段代码的意图一目了然,但开发团队可能对这种情况下的括号放置有自己的编码规范,以消除任何歧义。
#include <stdio.h>
int main() {
for (int i = 0, j = 11; i < j; i++)
if (i & 1 == 1)
// prints 1 3 5 7 9
printf("%d ", i);
}
我们已经看到,块内定义的值无法从块外访问。
我们也看到,块外定义的值通常可以从块内访问。
如果在定义块内变量时使用了块外某个变量的标识符,那么块外的这个变量就可能在块内被隐藏。
在下面的例子中,number这个标识符同时用作函数级变量和块级变量的名字。
我们说,这个块级变量隐藏或者说_遮蔽_了函数级变量。
#include <stdio.h>
int main() {
int number = 1;
// prints number in function is 1
printf("number in function is %d\n", number);
if (number == 1) {
// the definition of a block-level variable using the same name
// shadows the function-level variable
int number = 2;
// prints number in function is 2
printf("number in if block is %d\n", number);
}
// prints number in function is 1
printf("number in function is %d\n", number);
}
如果试图遮蔽一个函数形参,编译器可能会报错。 下面的例子
#include <stdio.h>
void print_number(int number) {
// will fail to compile
int number = 2;
printf("number in function is %d\n", number);
}
int main() {
int number = 1;
printf("number in main is %d\n", number);
print_number(number);
printf("number in main is %d\n", number);
}
会在两个平台上产生以下错误
/tmp/jIvhD5UOop.c: In function 'print_number':
/tmp/jIvhD5UOop.c:5:9: error: 'number' redeclared as different kind of symbol
5 | int number = 2;
| ^~~~~~
/tmp/jIvhD5UOop.c:3:23: note: previous definition of 'number' was here
3 | void print_number(int number) {
| ~~~~^~~~~~
以及
redefinition of formal parameter 'number'