CodeGym /課程 /C++ SELF /深入變數

深入變數

C++ SELF
等級 6, 課堂 1
開放

1. 回顧變數

當你寫程式時,它會不斷在計算某些東西:總和、餘數、嘗試次數、當前分數、選定的使用者名稱。如果不把這些東西存放在某處,程式就像金魚:三秒鐘後上下文就丟失了。變數的用途就是讓程式在執行期間擁有「記憶」。

在深入之前,讓我們回想一下你已經知道的變數相關概念。

可以將變數想像成貼了標籤的小容器。標籤是變數名稱,容器有特定的類型(決定可以存放什麼「形狀」的資料),裡面則是當前的值。而這三要素——類型 + 名稱 + 值——就是變數的工作模型。

先看一個小表格,幫助鞏固這個概念:

這是什麼 程式碼表示 代表什麼
類型
int, std::string
「可以存放什麼值」
名稱
score, age, name
「程式中如何引用它」
值
10, "Alex"
「現在裡面放著什麼」

我們暫時不深入各種類型(那會是在下一堂課)。但要記住:類型不是裝飾品。 它訂定了遊戲規則:對變數可以做什麼、以及可放入哪些值。

2. 宣告與初始化

初學者常混淆的一點是,常在同一行做兩件事。當你寫下 int x = 10;, 你同時「宣告」變數(告訴編譯器它的類型與名稱)並「初始化」(給予初始值)。這有點像開一個銀行帳戶並立刻存入第一筆錢——不是必要,但很合理。

在本課程中我們會採用簡單的安全規則:變數在宣告時應該被賦予初始值。 這能省時並避免當你誤用來自「無處」的值時出現奇怪的 Bug。

小範例:變數出現並立即拿到數值。

#include <iostream>

int main() {
    int score = 10;                 // 建立變數 score,值為 10
    std::cout << score << '\n';      // 10
}

注意:關鍵字 int 是宣告的一部分。只要你還沒寫出類型,變數就還沒「被建立」。名稱也很重要:編譯器必須知道如何在之後引用它。

另一個範例:變數由表達式初始化。這仍然是初始化——只是右邊不是「現成的數字」,而是一個運算式。

#include <iostream>

int main() {
    int a = 3;
    int b = 4;

    int sum = a + b;                // sum 被建立並立即得到 7
    std::cout << sum << '\n';        // 7
}

這裡有一個有用的口訣:心裡默念「建立 sum,計算 a + b,把結果存到 sum」。

3. 賦值:改變已存在變數的值

變數建立後,你可以更新它裡面的內容。這是另一種動作——賦值。這裡是本講的關鍵:在 C++ 中,符號 = 的意思不是「相等」,而是「寫入新值」。

記住一個簡單的分法:

  • 初始化——「建立變數」;
  • 賦值——「更新已存在的變數」。

範例:先建立變數,然後改變它的值。

#include <iostream>

int main() {
    int score = 10;                 // 建立
    score = 20;                     // 改變

    std::cout << score << '\n';     // 20
}

現在一個更「活」的範例:使用舊值來更新。

#include <iostream>

int main() {
    int score = 10;

    score = score + 5;              // 右邊計算 10 + 5,結果 15 寫入 score
    std::cout << score << '\n';     // 15
}

這裡顯示了一條重要的賦值規則:先評估右側,再把結果寫入左側。

也就是說,這不是「等式」。更像是一條命令:「取出目前的 score,加上 5,並存回 score」。

4. 表達式與運算子 = 和 ==

右側是表達式

當你看到像 x = ...; 這樣的語句時,右側通常是一個表達式。表達式是一段能被求值為單一值的程式碼。那裡可以是單純的數字、變數、公式,甚至是從 std::cin 讀入的結果(不過通常會把 cin 放在單獨一行以保持可讀性)。

我們來看幾個變體以訓練判斷力。

#include <iostream>

int main() {
    int x = 5;
    int y = 2;

    int a = x;                      // 右側的表達式是單純的 x
    int b = x + y;                  // 表達式是加法
    int c = (x + y) * 10;           // 表達式是一個比較複雜的公式

    std::cout << a << ' ' << b << ' ' << c << '\n';  // 5 7 70
}

我們要養成的習慣是:在每一行賦值時問自己:「右側在計算什麼?」以及「結果要寫到左側的哪裡?」。

把這個變成直覺後,閱讀條件、迴圈與各種公式都會容易得多。

= —— 不是比較

把 = 與「等於」混淆是個經典問題,幾乎可以算作程式新手的入門儀式。幾乎每個人至少寫過一次像在條件中寫 if (x = 10),然後驚訝於程式「創造性」的行為。

重點:

  • = —— 賦值(改變變數),
  • == —— 比較(回傳 true 或 false)。

你可能已經學過比較,今天我們多加一個強調:在條件中把 = 寫成 == 的錯誤,是語意上的錯誤,而非語法錯誤。有時編譯器甚至不會像你預期的那樣報錯。

小範例(示範「不要這樣做」,但不深入解釋):

#include <iostream>

int main() {
    int x = 3;

    // if (x = 10) {                 // 不好:你把 x 改為 10
    //     std::cout << "x is 10\n";
    // }

    if (x == 10) {                   // 好:你是在比較 x 與 10
        std::cout << "x is 10\n";
    } else {
        std::cout << "x is not 10\n"; // x is not 10
    }
}

我們現在不詳細解釋為何 if (x = 10) 可能會通過編譯。重點是實務規則:如果你心裡念到「檢查是否相等」——程式碼裡幾乎肯定應該寫 ==。

5. 可見範圍:變數在哪裡「可見」

當程式變大時,會產生一個實際的問題:「這個變數在哪裡能被存取?」在 C++ 中,答案與大括號 { ... } 有關。程式區塊決定了可見範圍:變數名稱從宣告的地方到當前區塊結束之間可見。

這有點像會議胸章:在會場內大家認得你,出了門保全會問:「請問您是誰?」(把編譯器想成保全會是個有趣的比喻。)

簡單範例:

#include <iostream>

int main() {
    int x = 1;

    {
        int y = 2;
        std::cout << (x + y) << '\n';    // 3
    }

    std::cout << x << '\n';             // 1
    std::cout << y << '\n';             // 錯誤:y 在此不存在
}

這裡 x 在外層區塊 main 中被宣告,因此在內外層都可見。y 則在內層區塊宣告,離開該區塊後就不存在了。

為何這很重要?因為 scope 幫助你保持思路清晰:變數只「活」在真正需要的地方,不會干擾其他程式碼。

scope 常透過 if 與迴圈自然產生。例如,若變數在 if 內宣告,通常在該 if 結束後就不會存在。

#include <iostream>

int main() {
    int temperature = 30;

    if (temperature > 25) {
        int extraWater = 2;
        std::cout << extraWater << '\n'; // 2
    }

    std::cout << extraWater << '\n';  // 錯誤:變數隨區塊結束而消失
}

對,這裡說「消失」其實很貼切:遇到右大括號 } 後,名稱就不再可用,彷彿從未存在過。

6. 小應用:存錢筒與餘額更新

為了讓變數不只是抽象哲學,我們來組一個小型的主控台情境,你可以在課程中擴充。假設它是個「存錢筒」或餘額系統,用來加錢與扣款。今天我們關注的是建立值與改變值之間的差別。

從最小可行的開始:有初始餘額與一次存入操作。

#include <iostream>

int main() {
    int balance = 0;                     // 初始值

    int deposit = 0;
    std::cin >> deposit;                 // 輸入存入金額

    balance = balance + deposit;          // 更新餘額
    std::cout << balance << '\n';         // 取決於輸入
}

這裡可以看到兩個層面:

第一層 —— 「建立變數」:balance、deposit。我們把它們宣告並初始化為 0,以確保有明確的起始值。

第二層 —— 「改變程式狀態」:我們讀入 deposit 並更新 balance。更新透過賦值完成,右側是表達式 balance + deposit。

加入另一個動作:支出。仍然是用同樣的變數與機制,只是多做一次賦值。

#include <iostream>

int main() {
    int balance = 0;

    int deposit = 0;
    std::cin >> deposit;

    balance = balance + deposit;

    int spend = 0;
    std::cin >> spend;

    balance = balance - spend;

    std::cout << balance << '\n';         // 最終餘額
}

如果你仔細看,會發現一個「模板」:

1) 建立變數,
2) 取得值,
3) 更新另一個變數的狀態。

這是程式設計中最常見的模式之一。今天的目標是要學會「用眼睛看到」這個模式,而不只是用手去打字。

賦值的流程圖:動作順序

有時不是程式碼,而是一張小圖更能幫助理解。賦值可以心裡分解成幾步:先計算右側,接著把結果寫入左側。這像是在咖啡廳點餐:先做好料理,然後放到盤子上;不能把還沒做好料理放到盤子上。

簡化的流程圖:

flowchart TD
    A["行:x = 表達式;"] --> B["計算右側的表達式"]
    B --> C["得到結果(單一值)"]
    C --> D["把結果寫入 x"]
    D --> E["x 現在存放新的當前值"]

如果你常常疑惑「為什麼會這樣算出來」,往往是因為你沒正確想像這個順序。當右側也包含 x(像 x = x + 5;)時尤其要注意:先計算,再寫入。

7. 常見錯誤

錯誤 1:在宣告之前使用變數。
看起來無傷大雅:你在上面寫了 std::cout << total << '\n';,然後在下面才加上 int total = 0;。對人類來說這「幾乎在附近」,但對編譯器而言順序是嚴格的:在變數被宣告之前,名稱不存在。解法很簡單:在第一次使用之前宣告變數,並盡量把宣告放在實際需要的最接近處。

錯誤 2:以為 = 是數學上的等號。
在數學上,x = x + 1 看起來是無意義的(微積分老師可能會抓粉筆)。在程式設計中,這是正常的更新指令:「取出舊的 x,加 1,存回去」。如果你一直記得「先計算右側 → 再寫入左側」,這類混淆會慢慢消失。

錯誤 3:在條件中混淆 = 與 ==。
這種情況會導致程式「執行但效果不對」。你本來想比較,卻在條件內改變了變數值。即使編譯器給出警告,新手也常忽略,因為「反正跑得起來」。紀律很簡單:如果你是在問「是否相等」,就寫 ==。

錯誤 4:把變數宣告得太早,導致過大的可見範圍。
有些新手喜歡在 main 開頭宣告十幾個變數,「這樣比較安心」。結果很多變數直到程式中段才使用,你忘了哪裡存了什麼,出現魔法數字與「為何它會改變?」的疑惑。更好的做法是在第一次需要之前才宣告變數——這樣 scope 較小,程式也更易讀。

錯誤 5:在區塊內宣告變數但期望它在外面仍可用。
常見情境:你在 if 內計算了 discount,然後在外面想套用到價格。但 discount 是在 { ... } 內宣告,離開區塊後就不存在。解法是:若變數應存在更久,就在外層宣告並在內層更新;反之,若變數只屬於該情境,就把使用留在區塊內。

1
任務
C++ SELF, 等級 6, 課堂 1
上鎖
雙倍總和
雙倍總和
1
任務
C++ SELF, 等級 6, 課堂 1
上鎖
交換位置
交換位置
1
任務
C++ SELF, 等級 6, 課堂 1
上鎖
每日存錢罐
每日存錢罐
1
任務
C++ SELF, 等級 6, 課堂 1
上鎖
卡片折扣
卡片折扣
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION