區塊

區塊 在 C

{other: "%{count} 個練習"}

關於 區塊

區塊是一組敘述與宣告,被歸為同一個語法單位。 區塊通常以(但並非總是)大括號定義。 區塊可以巢狀於函式之中,也可以巢狀於其他區塊之中。

在下面的例子中,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'
透過 GitHub 編輯 連結會在新視窗或分頁中開啟