學習目標
讀完本章後,你應該能夠:
- 掌握條件判斷:
if/else if/else,以及 C++17 的 if 初始化語句 - 熟練三元(條件)運算子,理解它與
if的取捨 - 學會多路分支:
switch/case,理解穿透與[[fallthrough]] - 理解邏輯運算子的短路求值及其實際用途
- 熟悉迴圈結構:
for、while、do-while、範圍for - 用範圍 for 搭配結構化綁定遍歷
map等容器 - 了解
break、continue與「跳出多層迴圈」的處理方式 - 認識常見迴圈慣用法、無窮迴圈寫法與逗號運算子
- 能夠撰寫巢狀迴圈解決實際問題
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 的限制
- 只能比較整數型別(
int、char、enum),不能比較std::string、float、double。 - 每個
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
for 與 while 可以互相改寫;慣例上「已知次數」用 for,「直到某條件成立」用 while。
7. do-while 迴圈
先執行一次迴圈主體,再判斷條件。保證至少執行一次。
int input;
do {
std::cout << "請輸入正數:";
std::cin >> input;
} while (input <= 0);
// 至少會要求輸入一次
do-while 的真實使用情境
do-while 在實務上比初學者想的更常用,典型場景是「先做一次,再決定要不要重複」:
- 輸入驗證:一定要先問一次,才知道輸入對不對(如上例)。
- 選單系統:先顯示選單、處理選擇,使用者選「離開」才結束。
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);
- 至少跑一輪的演算法:例如「重複擲骰子直到擲出 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); // 較少見
無窮迴圈常用於「事件迴圈」「伺服器主迴圈」「重試直到成功」,搭配 break 或 return 離開。
常見迴圈慣用法(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。
重點整理
if/else if/else由上而下命中即止,條件順序很重要。- 三元運算子是「運算式」會回傳值,適合選值;多行邏輯用
if。 switch適合整數對多固定值,記得加break;故意穿透用[[fallthrough]]標示。&&/||具短路求值,把「保護條件」放前面可避免越界、除以零、空指標解參考。for適合已知次數,while適合未知次數,do-while保證至少執行一次(輸入驗證、選單)。- 範圍 for 是遍歷容器首選;搭配結構化綁定
[k, v]遍歷 map 更直覺。 break只跳最內層;跳多層優先「抽成函式 + return」,避免goto。- 無窮迴圈用
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>,用四個計數器,搭配 if/continue。
練習 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 初始化語句、跳出多層迴圈