學習目標

讀完本章後,你應該能夠:

  • 掌握條件判斷:if / else if / else,以及 C++17 的 if 初始化語句
  • 熟練三元(條件)運算子,理解它與 if 的取捨
  • 學會多路分支:switch / case,理解穿透與 [[fallthrough]]
  • 理解邏輯運算子的短路求值及其實際用途
  • 熟悉迴圈結構:forwhiledo-while、範圍 for
  • 用範圍 for 搭配結構化綁定遍歷 map 等容器
  • 了解 breakcontinue 與「跳出多層迴圈」的處理方式
  • 認識常見迴圈慣用法、無窮迴圈寫法與逗號運算子
  • 能夠撰寫巢狀迴圈解決實際問題

1. 條件判斷(if / else)

基本語法

if (條件) {
    // 條件為 true 時執行
}

if (條件) {
    // true
} else {
    // false
}

if (條件1) {
    // 條件1 為 true
} else if (條件2) {
    // 條件2 為 true
} else {
    // 以上皆非
}

條件運算式的結果會被轉成 bool。任何非零的數值都視為 true,0 視為 false

int n = 5;
if (n) {                 // 等同於 if (n != 0)
    std::cout << "n 不是 0\n";
}

範例:成績等第

int score = 85;

if (score >= 90) {
    std::cout << "A(優)" << std::endl;
} else if (score >= 80) {
    std::cout << "B(良)" << std::endl;       // 會印這行
} else if (score >= 70) {
    std::cout << "C(可)" << std::endl;
} else if (score >= 60) {
    std::cout << "D(差)" << std::endl;
} else {
    std::cout << "F(不及格)" << std::endl;
}

注意 else if 鏈是「由上而下、命中即止」。因為 85 已經滿足 >= 80,就不會再去檢查後面的條件,所以條件順序很重要:要從最嚴格(最大)排到最寬鬆。

巢狀 if

if (age >= 18) {
    if (hasLicense) {
        std::cout << "可以開車" << std::endl;
    } else {
        std::cout << "需要先考駕照" << std::endl;
    }
}

永遠加大括號

if (x > 0)
    std::cout << "正數\n";       // 沒加 {},只有這一行屬於 if

// 危險範例:
if (x > 0)
    std::cout << "正數\n";
    std::cout << "永遠會執行\n"; // 這行其實不屬於 if!縮排騙了你

C++ Core Guidelines(ES.74 精神):即使只有一行,也建議加上 {}。這能避免日後加第二行時忘記補大括號,導致邏輯錯誤(著名的 Apple「goto fail」安全漏洞就與此類問題有關)。

C++17 — if 中宣告變數(初始化語句)

C++17 允許在 if 裡先宣告一個只在這個 if/else 內有效的變數:

// 語法:if (初始化; 條件)
if (auto it = myMap.find(key); it != myMap.end()) {
    std::cout << "找到了:" << it->second << std::endl;
} else {
    std::cout << "找不到 key\n";
}
// it 在這裡已離開作用域,不會污染外部命名空間

好處是把變數的作用域限制到最小,避免不小心在別處誤用,也讓程式碼更整潔。


2. 三元(條件)運算子

條件 ? 真值 : 假值 是一個運算式(會回傳值),而 if敘述(不回傳值)。這是兩者最關鍵的差別。

int a = 10, b = 20;
int maxVal = (a > b) ? a : b;        // 取較大值,maxVal = 20

// 因為是運算式,可以直接嵌在輸出裡:
std::cout << "結果:" << ((a > b) ? "a 大" : "b 大") << '\n';

何時用三元、何時用 if?

情境 建議
依條件選出一個值並賦值 / 輸出 用三元,簡潔
依條件執行不同動作(多行邏輯) if,清楚
巢狀超過一層 改用 if,否則難讀
// ✅ 適合三元
std::string parity = (n % 2 == 0) ? "偶數" : "奇數";

// ❌ 巢狀三元可讀性差,不建議
std::string g = (s >= 90) ? "A" : (s >= 80) ? "B" : (s >= 70) ? "C" : "F";

⚠️ 三元運算子的「真值」與「假值」兩個分支必須是相容的型別,編譯器會嘗試找出共同型別。例如 cond ? 1 : 2.0 結果會是 double


3. switch / case

適合用在一個整數型變數對應多個固定值的情境,通常比一長串 else if 更清楚、也更有效率。

基本語法

switch (變數) {
    case 值1:
        // 處理值1
        break;      // 必須加 break,否則會穿透到下一個 case
    case 值2:
        // 處理值2
        break;
    default:        // 以上都不符合時執行
        // 預設處理
        break;
}

範例:星期判斷

int day = 3;
switch (day) {
    case 1: std::cout << "星期一" << std::endl; break;
    case 2: std::cout << "星期二" << std::endl; break;
    case 3: std::cout << "星期三" << std::endl; break;   // 會印這行
    case 4: std::cout << "星期四" << std::endl; break;
    case 5: std::cout << "星期五" << std::endl; break;
    case 6: std::cout << "星期六" << std::endl; break;
    case 7: std::cout << "星期日" << std::endl; break;
    default: std::cout << "無效的日期" << std::endl; break;
}

穿透(Fall-through)— 故意共用程式碼

故意不加 break,讓多個 case 共用同一段程式碼:

switch (day) {
    case 1: case 2: case 3: case 4: case 5:
        std::cout << "平日" << std::endl;
        break;
    case 6: case 7:
        std::cout << "假日" << std::endl;
        break;
}

[[fallthrough]](C++17)— 表達「我是故意的」

「忘記加 break」是 switch 最常見的 bug,所以 -Wextra 等會對「疑似忘記 break 的穿透」發出警告。當你真的想要穿透時,用 C++17 的 [[fallthrough]] 屬性明確告訴編譯器與讀者:

switch (level) {
    case 3:
        std::cout << "授予管理員權限\n";
        [[fallthrough]];          // 明確標示:故意穿透,不是漏寫 break
    case 2:
        std::cout << "授予編輯權限\n";
        [[fallthrough]];
    case 1:
        std::cout << "授予閱讀權限\n";
        break;
    default:
        std::cout << "無權限\n";
        break;
}
// level = 3 會印出三行(管理員同時擁有編輯與閱讀權限)

case 內宣告變數要小心

switch (x) {
    case 1:
        int y = 10;        // ⚠️ 可能編譯錯誤:跳過初始化
        break;
    case 2:
        // ...
        break;
}

若要在 case 內宣告變數,請用大括號建立自己的區塊:

switch (x) {
    case 1: {
        int y = 10;        // OK,限制在這個區塊內
        std::cout << y;
        break;
    }
}

switch 的限制

  • 只能比較整數型別intcharenum),不能比較 std::stringfloatdouble
  • 每個 case 的值必須是編譯期常數,且不可重複。
  • 需要比較字串時,改用 if / else if

4. 短路求值(Short-circuit Evaluation)

邏輯運算子 &&|| 採用短路求值:一旦結果已能確定,就不再計算右邊。

  • A && B:若 A 為 false,整體必為 false,B 不會被計算
  • A || B:若 A 為 true,整體必為 true,B 不會被計算

用途一:避免錯誤操作(先檢查再使用)

int arr[5] = {1, 2, 3, 4, 5};
int i = 10;

// 先確認 i 在範圍內,才存取 arr[i],避免越界
if (i >= 0 && i < 5 && arr[i] == 3) {
    std::cout << "找到了\n";
}

// 避免除以零
int n = 0;
if (n != 0 && 100 / n > 5) {     // n == 0 時右邊不執行,安全
    std::cout << "...\n";
}

用途二:避免對空指標解參考

// 若 ptr 為 nullptr,左邊 false,右邊不會去讀 *ptr(避免崩潰)
if (ptr != nullptr && *ptr > 0) {
    // ...
}

順序很重要!上面若寫成 *ptr > 0 && ptr != nullptr,當 ptr 為 null 時就會先解參考而崩潰。把「保護條件」放在前面是短路求值最重要的慣用法。


5. for 迴圈

基本語法

for (初始化; 條件; 更新) {
    // 迴圈主體
}

執行順序:① 初始化(只做一次)→ ② 檢查條件 → ③ 執行主體 → ④ 更新 → 回到 ②。

範例

// 輸出 1 到 10
for (int i = 1; i <= 10; i++) {
    std::cout << i << " ";
}
// 輸出:1 2 3 4 5 6 7 8 9 10

// 倒數
for (int i = 10; i >= 1; i--) {
    std::cout << i << " ";
}

// 每次加 2
for (int i = 0; i <= 20; i += 2) {
    std::cout << i << " ";
}

for 的彈性:多變數與逗號運算子

for 的初始化與更新部分可以用逗號運算子塞入多個動作:

// 從兩端往中間靠攏
for (int i = 0, j = 10; i < j; i++, j--) {
    std::cout << "(" << i << "," << j << ") ";
}

逗號運算子a, b 會先計算 a,再計算 b,整個運算式的值是 b。它在 for 的更新區(i++, j--)最常見。除此之外請少用,容易造成誤解。

三個部分都可省略

int k = 0;
for (; k < 5; ) {     // 省略初始化與更新
    std::cout << k << " ";
    k++;
}

for (;;) {            // 三者全省 → 無窮迴圈(等同 while(true))
    // 需要自己用 break 跳出
    break;
}

6. while 迴圈

先判斷條件,條件為 true 才執行迴圈主體。適合不確定執行次數的情境。

int count = 0;
while (count < 5) {
    std::cout << count << " ";
    count++;
}
// 輸出:0 1 2 3 4

forwhile 可以互相改寫;慣例上「已知次數」用 for,「直到某條件成立」用 while


7. do-while 迴圈

先執行一次迴圈主體,再判斷條件。保證至少執行一次

int input;
do {
    std::cout << "請輸入正數:";
    std::cin >> input;
} while (input <= 0);
// 至少會要求輸入一次

do-while 的真實使用情境

do-while 在實務上比初學者想的更常用,典型場景是「先做一次,再決定要不要重複」:

  1. 輸入驗證:一定要先問一次,才知道輸入對不對(如上例)。
  2. 選單系統:先顯示選單、處理選擇,使用者選「離開」才結束。
int choice;
do {
    std::cout << "1) 開始  2) 設定  0) 離開\n選擇:";
    std::cin >> choice;
    switch (choice) {
        case 1: std::cout << "開始遊戲\n"; break;
        case 2: std::cout << "進入設定\n"; break;
        case 0: std::cout << "再見\n";     break;
        default: std::cout << "無效選項\n"; break;
    }
} while (choice != 0);
  1. 至少跑一輪的演算法:例如「重複擲骰子直到擲出 6」。

判斷準則:如果「主體無論如何都該先執行一次」,就用 do-while;否則用 while


8. 範圍 for 迴圈(Range-based for,C++11)

用於遍歷容器或陣列,語法簡潔且不會有索引越界的風險。

#include <vector>

int arr[] = {10, 20, 30, 40, 50};
for (int val : arr) {                // 複製每個元素到 val
    std::cout << val << " ";
}

std::vector<std::string> fruits = {"蘋果", "香蕉", "橘子"};
for (const auto& fruit : fruits) {   // const 參考:不複製、不修改(推薦)
    std::cout << fruit << " ";
}

std::vector<int> nums = {1, 2, 3};
for (auto& n : nums) {               // 非 const 參考:可修改原始元素
    n *= 2;
}

選擇迴圈變數型態的準則:

寫法 意義 何時用
for (auto x : c) 複製一份 元素小(如 int)、不需改原值
for (const auto& x : c) 唯讀參考 只讀取,元素較大(如 string、結構)
for (auto& x : c) 可寫參考 要修改容器內的元素

範圍 for + 結構化綁定(C++17)

遍歷 std::map(或任何回傳 pair / tuple 的容器)時,可以用結構化綁定把 pair 直接拆成有名字的變數,非常好讀:

#include <map>
#include <string>

std::map<std::string, int> scores = {
    {"小明", 90}, {"小華", 85}, {"小美", 95}
};

// 舊寫法:用 .first / .second(較不直覺)
for (const auto& p : scores) {
    std::cout << p.first << ":" << p.second << '\n';
}

// C++17 結構化綁定:直接命名為 name 與 score
for (const auto& [name, score] : scores) {
    std::cout << name << ":" << score << '\n';
}

結構化綁定也能用在一般陣列或 pair 上:

std::pair<int, int> point = {3, 4};
auto [x, y] = point;             // x = 3, y = 4

9. break 和 continue

break — 立即跳出(最內層)迴圈

for (int i = 1; i <= 100; i++) {
    if (i == 5) break;     // 遇到 5 就停止
    std::cout << i << " ";
}
// 輸出:1 2 3 4

continue — 跳過本次迭代,進入下一輪

for (int i = 1; i <= 10; i++) {
    if (i % 3 == 0) continue;  // 跳過 3 的倍數
    std::cout << i << " ";
}
// 輸出:1 2 4 5 7 8 10

⚠️ 在 while/do-while 中使用 continue 時要小心:它會直接跳到條件判斷,如果你的「更新」寫在 continue 之後,就會被跳過而造成無窮迴圈。


10. 巢狀迴圈與跳出多層

// 九九乘法表
for (int i = 1; i <= 9; i++) {
    for (int j = 1; j <= 9; j++) {
        std::cout << i << "×" << j << "=" << i * j << "\t";
    }
    std::cout << std::endl;
}

巢狀迴圈的總執行次數是外層 × 內層,要注意效能(時間複雜度)。

如何跳出「多層」迴圈?

break 只會跳出最內層那一圈。想一次跳出多層,有幾種乾淨的做法:

做法一:用旗標變數

bool found = false;
for (int i = 0; i < N && !found; i++) {
    for (int j = 0; j < M; j++) {
        if (grid[i][j] == target) {
            found = true;
            break;           // 跳出內層
        }
    }
}

做法二(推薦):把迴圈抽成函式,用 return

bool search(const int grid[][M], int target) {
    for (int i = 0; i < N; i++)
        for (int j = 0; j < M; j++)
            if (grid[i][j] == target)
                return true;     // 直接離開整個函式 = 跳出所有迴圈
    return false;
}

做法三:goto(不建議,但你會看到)

C++ 沒有像 Java 那樣的「標籤式 break」。有些 C 程式碼會用 goto 跳到迴圈外的標籤:

for (int i = 0; i < N; i++) {
    for (int j = 0; j < M; j++) {
        if (grid[i][j] == target) goto done;
    }
}
done:
std::cout << "結束\n";

C++ Core Guidelines(ES.76):避免使用 goto,它會讓控制流程難以追蹤。少數可接受的場景是「從深層巢狀迴圈跳出」,但即使如此,改用做法二(抽成函式 + return)幾乎總是更好


11. 無窮迴圈與常見慣用法

三種寫無窮迴圈的方式

while (true) { /* ... */ break; }    // 最常見、最易讀
for (;;)     { /* ... */ break; }    // 傳統 C 風格
do { /* ... */ } while (true);        // 較少見

無窮迴圈常用於「事件迴圈」「伺服器主迴圈」「重試直到成功」,搭配 breakreturn 離開。

常見迴圈慣用法(Idioms)

// ① 累加 / 求總和
int sum = 0;
for (int x : data) sum += x;

// ② 計數符合條件的元素
int evenCount = 0;
for (int x : data) if (x % 2 == 0) ++evenCount;

// ③ 找最大值
int maxVal = data[0];
for (int x : data) if (x > maxVal) maxVal = x;

// ④ 用索引遍歷(需要索引時)
for (std::size_t i = 0; i < data.size(); ++i) {
    std::cout << i << ": " << data[i] << '\n';
}

小提醒:std::vector::size() 回傳的是無號的 size_t。用索引倒數遍歷時,別寫 for (size_t i = v.size()-1; i >= 0; i--)(會變無窮迴圈,回顧 Ch02 的 unsigned 陷阱)。改用 for (int i = (int)v.size()-1; i >= 0; --i) 或範圍 for。


重點整理

  1. if / else if / else 由上而下命中即止,條件順序很重要。
  2. 三元運算子是「運算式」會回傳值,適合選值;多行邏輯用 if
  3. switch 適合整數對多固定值,記得加 break;故意穿透用 [[fallthrough]] 標示。
  4. && / || 具短路求值,把「保護條件」放前面可避免越界、除以零、空指標解參考。
  5. for 適合已知次數,while 適合未知次數,do-while 保證至少執行一次(輸入驗證、選單)。
  6. 範圍 for 是遍歷容器首選;搭配結構化綁定 [k, v] 遍歷 map 更直覺。
  7. break 只跳最內層;跳多層優先「抽成函式 + return」,避免 goto
  8. 無窮迴圈用 while(true) 最易讀;熟記累加、計數、找極值等慣用法。

常見錯誤與陷阱

錯誤 說明
if (x = 5) 應該用 == 比較,= 是賦值(結果永遠為 true)
switch 忘記 break 會穿透到下一個 case,產生非預期行為
無窮迴圈 忘記更新迴圈變數,導致條件永遠為 true
迴圈邊界錯誤 < vs <= 差一個(off-by-one error)
空迴圈主體 for (int i = 0; i < 10; i++); 結尾多了 ;,主體是空的
浮點數作為迴圈條件 浮點精度問題可能導致迴圈次數不對
continue 跳過更新 在 while 中 continue 可能跳過遞增,造成無窮迴圈
範圍 for 複製大物件 忘記用 const auto&,遍歷字串/結構時造成大量複製
想用 break 跳多層 break 只跳一層,需用旗標、return 或重構
switch 比較字串 switch 不能用 std::string,需改 if / else if

練習題

練習 1(基礎):FizzBuzz

印出 1 到 100,但 3 的倍數印 "Fizz",5 的倍數印 "Buzz",同時是 3 和 5 的倍數印 "FizzBuzz",其餘印數字本身。 - 提示:先判斷「同時是 3 與 5 的倍數」(即 15 的倍數),再判斷個別情況,順序很重要。

練習 2(基礎):奇偶與正負分類

用範圍 for 遍歷一個 std::vector<int>,統計其中正數、負數、零的個數,以及偶數的個數,最後輸出結果。 - 提示:#include <vector>,用四個計數器,搭配 ifcontinue

練習 3(中級):質數判斷

撰寫程式判斷一個整數 n 是否為質數;接著印出 2 到 100 之間所有的質數。 - 提示:用內層迴圈從 2 試除到 √n(i * i <= n),一旦整除就 break 並標記非質數。

練習 4(中級):選單系統(do-while)

do-while + switch 寫一個重複顯示的選單:1) 問候、2) 顯示目前時間文字、0) 離開。輸入無效時提示錯誤並重新顯示選單,選 0 才結束。 - 提示:迴圈條件為 while (choice != 0),用 default 處理無效輸入。

練習 5(中級):星號金字塔

使用巢狀迴圈印出以下對稱金字塔(n = 5):

    *
   ***
  *****
 *******
*********
  • 提示:第 i 列先印 n - i 個空格,再印 2 * i - 1 個星號。

練習 6(挑戰):二維搜尋與跳出多層

給定一個二維陣列(例如 5×5),找出第一個等於目標值的位置並輸出其 (列, 行)。用「抽成函式 + return」的方式跳出雙層迴圈,而不要用 goto。 - 提示:寫一個回傳 bool 的函式,用兩個輸出參數(或 std::pair)回傳座標;找到就 return

練習 7(挑戰):成績統計(結構化綁定)

建立一個 std::map<std::string, int> 存放學生姓名與分數,用範圍 for + 結構化綁定 [name, score] 遍歷,計算平均分、找出最高分的學生,並列出所有不及格(< 60)的學生。 - 提示:#include <map>,遍歷時累加總分與計數;用一個變數記住目前最高分者的名字。


對應程式碼檔案

  • if_else.cpp — 條件判斷示範
  • switch_case.cpp — switch / case 示範
  • loops.cpp — 各種迴圈示範(for、while、do-while、range-based for)
  • modern_control.cpp — 現代控制流程:結構化綁定、[[fallthrough]]、短路求值、if 初始化語句、跳出多層迴圈