區塊是一組敘述與宣告,被歸為同一個語法單位。 區塊通常以(但並非總是)大括號定義。 區塊可以巢狀於函式之中,也可以巢狀於其他區塊之中。
在下面的例子中,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'