語法書 / AA 競程語法書 上冊 / 第三單元 / 變數範圍(Scope)

3.8 變數範圍(Scope)

圖 3-3:變數作用域(Scope)

當程式變大時,變數會越來越多。我們需要理解每個變數在程式的哪個地方可以被使用。這就是變數範圍(Scope)的概念。

區塊(Block)

在 C++ 中,大括號 {} 圍起來的區域稱為一個區塊。每個區塊有自己的作用域。

{
    // 這是一個區塊
    int x = 5;  // x 在這個區塊內宣告
    cout << x;  // 在這裡可以使用 x
}
// 這裡無法使用 x(超出了它的作用域)

作用域規則

  • 變數只能在它被宣告的區塊以及更深層的巢狀區塊內使用
  • 離開宣告它的區塊後,變數自動銷毀——意思是出了區塊,這個變數名稱就不能再用了(想用就得重新宣告)

範例程式碼:作用域示意

#include <iostream>
using namespace std;

int main() {  // main 函式的區塊開始
    int x = 10;  // 在 main 區塊宣告 x

    cout << "outer x: " << x << endl;  // 輸出:10

    if (x > 5) {  // if 的區塊開始
        int y = 20;  // 在 if 區塊宣告 y
        cout << "inner y: " << y << endl;  // 輸出:20
        cout << "inner sees x: " << x << endl;  // 輸出:10
    }  // if 區塊結束,y 銷毀

    // cout << y << endl;  // ✗ 編譯錯誤:y 超出作用域
    cout << "outer still has x: " << x << endl;  // ✓ 輸出:10

    return 0;
}

執行結果

outer x: 10
inner y: 20
inner sees x: 10
outer still has x: 10

同一個區塊不能重複宣告同名變數

如果在同一個區塊裡宣告兩個同名變數,編譯器會直接報錯:

int x = 5;
int x = 10;   // ✗ 編譯錯誤:redeclaration of 'int x'

看到 error: redeclaration of ...,就是同一個區塊裡把同一個名字宣告了兩次——改名,或刪掉其中一個宣告。

那如果是不同層的區塊宣告同名變數呢?這就是接下來的「變數遮蔽」。

變數遮蔽(Variable Shadow)

如果在內層區塊宣告一個和外層同名的變數,這是合法的,但內層的變數會「遮蔽」(隱藏)外層的變數。這很容易造成 bug。

#include <iostream>
using namespace std;

int main() {
    int x = 10;  // 外層的 x

    cout << "outer x: " << x << endl;  // 輸出:10

    {
        int x = 20;  // ✗ 內層的 x(同名,遮蔽了外層的 x)
        cout << "inner x: " << x << endl;  // 輸出:20
    }

    cout << "outer x: " << x << endl;  // 輸出:10(外層 x 沒變)

    return 0;
}

執行結果

outer x: 10
inner x: 20
outer x: 10

細節:區域變數從「宣告的那一行」才開始存在

內層那個同名變數,並不是一進區塊就生效,而是從它被宣告的那一行起才算數。在那行之前用到同一個名字,指的仍然是外層的變數:

int x = 10;      // 外層 x

{
    x = 2;       // 這行還沒宣告內層 x → 改到的是「外層」的 x
    int x = 5;   // 從這行起,x 才變成內層的變數
    cout << x;   // 輸出:5(內層 x)
}

cout << x;       // 輸出:2(外層 x 剛剛被改成 2 了)

所以「先用、後宣告」同一個名字時,前後其實動到兩個不同的變數——很容易看走眼。(全域變數也一樣:函式裡在宣告同名區域變數之前用到那個名字,改的是全域的那個。)

全域變數(Global Variables)

如果在所有函式外(也就是 main 之外——main 本身就是一個函式)宣告變數,它就是全域變數,可以在任何地方使用(但這通常不是好習慣):

#include <iostream>
using namespace std;

int global_x = 100;  // 全域變數

int main() {
    cout << global_x << endl;  // 可以使用
    return 0;
}

生命週期與宣告位置建議

變數從宣告的那一行誕生,到離開它所在的區塊時銷毀——這段期間稱為它的生命週期(lifetime)

那實務上該把變數宣告在哪裡?兩個建議:

  • 在第一次用到的地方宣告,宣告時就初始化。變數活得越短,越不容易被不相關的程式碼改壞;Google 的 C++ 風格指南也是同樣的建議。
  • 需要「一直記住東西」的變數,宣告在夠外層的區塊。例如第四單元會遇到:累加總和的變數若宣告在迴圈裡面,每一圈都會重新誕生、重新初始化,前面累加的結果就全丟了——這種要跨迴圈保留的變數,必須宣告在迴圈外。

延伸閱讀:Google C++ 風格指南:區域變數(中文版)

動手試試看

寫一個程式,在 main 函式中宣告 x = 5,然後在一個 if 區塊內宣告另一個 x = 10。輸出兩層的 x,驗證遮蔽的效果。然後,修改程式使用不同的變數名,看清楚之後程式更容易理解。