i < n ? body i++ done
PREREQ P2

流程控制:把迴圈讀成複雜度

先備知識 · 語法與互動
bool|&& 短路|switch fall-through|while|for(初;條件;增)|for (T& x : v)
向下捲動開始互動
📌 本頁使用方式(先備知識 · 語法與互動)

先讀語法與例子,再操作互動圖解並完成四選一自測。忘記寫法時回看速查表,最後用中英詞彙卡複習。

CONTENTS · 內容目錄
PROLOGUE · 開場

先決定走哪條路,再決定重複幾次

上一頁的敘述大多由上往下執行。這頁加入兩種能力:用 if 或 switch 依條件選擇敘述;用 while 或 for 重複敘述。先看各形式適合什麼情況,接著逐一拆開括號、條件、大括號與更新動作。

結構最小外觀什麼時候執行適合情境
ifif (ready) { … }條件為真時執行依條件決定是否處理
switchswitch (choice) { case 1: …; break; }跳到相符標籤後執行同一個整數有數個固定選項
whilewhile (remaining > 0) { … }每次先檢查條件重複到條件不成立
forfor (int i = 0; i < 3; ++i) { … }初始化後反覆檢查與更新明確的計數或走訪

先辨認你要的是「選擇」還是「重複」,再看條件在什麼時候檢查。條件與更新要一起讀:同一個 while 外觀,搭配不同的更新動作,就可能執行不同次數。下面逐節示範,最後再用巢狀迴圈比較次數。

C++ 的流程控制長得跟 Python 很像,但有四個地方需要特別留意:大括號、switch 的掉落、三段式 for 的執行時機,以及範圍 for 中決定「改得動還是改不動」的 &。這四個各有一節。

敘述 1 敘述 2 敘述 3 else 主體 if 主體 迴圈主體 條件 條件 false true true false 回跳 敘述; 敘述; if / else if / else while / for ① 循序 ② 選擇 ③ 迴圈

▲ 三種結構就是全部了:循序一行接一行往下走;選擇在菱形處分岔,兩條路走完會合成一條;迴圈的菱形多一條往回跳的線,條件成立就再繞一圈,不成立才從旁邊離開。本頁後面每一節都在講這三張圖裡的某一個角落。

📌 本頁定位

本頁整理後續程式常用的流程控制。像 divisor != 0 && numerator / divisor > 3 這類保護條件為什麼不能顛倒,答案就在 PART 01。

PART 01 · 布林與比較

bool、六個比較運算子,以及避免無效運算的短路求值

C++ 的 bool 只有兩個值:truefalse。轉成整數時分別得到 1 與 0;整數轉成 bool 時,0 為假、非零為真。這是型別轉換規則,不能據此推定 bool 的底層表示方式。

布林與比較運算子的實際行為
#include <iostream> using namespace std; int main() { bool a = true; bool b = false; cout << boolalpha; // 讓 bool 印成 true / false cout << "a = " << a << ",b = " << b << endl; cout << "3 < 5 -> " << (3 < 5) << endl; cout << "3 == 5 -> " << (3 == 5) << endl; cout << "3 != 5 -> " << (3 != 5) << endl; cout << "a && b -> " << (a && b) << endl; cout << "a || b -> " << (a || b) << endl; cout << "!a -> " << (!a) << endl; cout << noboolalpha; // 換回數字 cout << "true 印成數字是 " << true << ",false 是 " << false << endl; cout << "非零整數當條件時是真:(bool)42 = " << (bool)42 << endl; cout << "空字元 '\\0' 轉成 bool = " << (bool)'\0' << endl; return 0; }
預期輸出
a = true,b = false
3 < 5    -> true
3 == 5   -> false
3 != 5   -> true
a && b   -> false
a || b   -> true
!a       -> false
true 印成數字是 1,false 是 0
非零整數當條件時是真:(bool)42 = 1
空字元 '\0' 轉成 bool = 0

boolalpha 這個操作子讓 coutbool 印成 truefalse,不加的話印出來是 1 與 0。最後兩行是重點:42 轉成 bool 是 1,'\0' 轉成 bool 是 0,也就是說 C++ 的條件不必是 bool,是整數就行

運算子唸法要注意什麼
== !=相等、不相等比較用兩個等號。一個等號是賦值
< <= > >=大小比較基本型別已有比較規則;自訂型別的比較可以另行定義,詳見 P9。
&&而且(and)左邊為假就不看右邊
||或者(or)左邊為真就不看右邊
!否定(not)Python 寫 not,C++ 寫驚嘆號

短路求值:&& 的右邊不一定會被執行

這是本頁最該記住的一件事。a && b 求值時,C++ 會先算 a;如果 a 已經是假,整個式子不可能為真,b 就完全不會被求值|| 反過來:左邊為真就直接收工。

誰被求值了,讓程式自己說
#include <iostream> using namespace std; // 兩個會「留下痕跡」的函式,方便看誰被求值了 bool lhs(bool v) { cout << " 左邊求值了" << endl; return v; } bool rhs(bool v) { cout << " 右邊求值了" << endl; return v; } int main() { cout << boolalpha; bool r; cout << "false && 右邊:" << endl; r = lhs(false) && rhs(true); cout << " 整式結果 " << r << endl; cout << "true && 右邊:" << endl; r = lhs(true) && rhs(false); cout << " 整式結果 " << r << endl; cout << "true || 右邊:" << endl; r = lhs(true) || rhs(false); cout << " 整式結果 " << r << endl; cout << "false || 右邊:" << endl; r = lhs(false) || rhs(true); cout << " 整式結果 " << r << endl; return 0; }
預期輸出
false && 右邊:
    左邊求值了
  整式結果 false
true && 右邊:
    左邊求值了
    右邊求值了
  整式結果 false
true || 右邊:
    左邊求值了
  整式結果 true
false || 右邊:
    左邊求值了
    右邊求值了
  整式結果 true

四組裡有兩組只印了「左邊求值了」——右邊那個函式連呼叫都沒有發生。這不是最佳化,是 C++ 標準規定的行為,你可以放心依賴它。

短路求值常用來保護只在特定條件下才安全的運算。例如除法前要先確認除數不為 0;若保護條件失敗,右側的除法便不會執行。

正確順序:先確認除數不為 0,再做除法
#include <iostream> using namespace std; int main() { int numerator = 24; int divisor = 0; // 左邊不成立時,右邊的除法完全不會執行 if (divisor != 0 && numerator / divisor > 3) { cout << "商大於 3" << endl; } else { cout << "條件不成立" << endl; } return 0; }
預期輸出
條件不成立

divisor != 0 不成立,因此 numerator / divisor 根本沒被求值,程式安全地走進 else

順序寫反:除法會先發生
#include <iostream> using namespace std; int main() { int numerator = 24; int divisor = 0; // 錯誤:還沒確認 divisor 就先做除法 if (numerator / divisor > 3 && divisor != 0) cout << "商大於 3" << endl; return 0; }
結果
[未定義行為:整數除以零]

只把 && 兩邊對調,程式便會先計算 numerator / divisor。整數除以零是未定義行為,不能依賴特定錯誤訊息或結束碼。

⚠️ 保護條件必須放在左邊

&& 左右兩邊的順序在這裡不是風格問題,是正確性問題。凡是右側運算有前提,先決條件都要放左邊。例如 i < v.size() && v[i] != x 先檢查索引,才安全讀取元素。

✅ 現代 C++ 對照

C++ 也支援 and、or、not,分別是 &&、||、! 的替代拼法。本頁使用符號形式;遇到文字形式時依相同規則閱讀即可。

PART 02 · 條件

if / else if / else:大括號不是可有可無

先記住三個語法位置:條件要用小括號包起來,else if 中間有空格,主體用大括號。省略大括號時,後來新增的敘述可能不在條件控制之內。

if (score >= 90) { grade = 'A'; } else if (score >= 80) { // 注意:else if 是兩個字 grade = 'B'; } else { grade = 'F'; }

C++ 靠大括號決定範圍,縮排對編譯器完全沒有意義。省略大括號時,if 只管接在它後面的那一個敘述,第二行縮排得再整齊也沒有用。

縮排騙了你,編譯器沒有
#include <iostream> using namespace std; int main() { int x = -5; cout << "沒加大括號的版本:" << endl; if (x > 0) cout << " x 是正數" << endl; cout << " 我以為這行也在 if 裡面" << endl; // 其實一定會執行 cout << "加了大括號的版本:" << endl; if (x > 0) { cout << " x 是正數" << endl; cout << " 這行才真的在 if 裡面" << endl; } cout << "(第二段什麼都沒印,因為 x = " << x << "" << endl; return 0; }
預期輸出
沒加大括號的版本:
  我以為這行也在 if 裡面
加了大括號的版本:
(第二段什麼都沒印,因為 x = -5)

x 是 −5,第一段的 if 明明不成立,「我以為這行也在 if 裡面」還是印出來了。第二段加了大括號,兩行就一起被跳過。這個 bug 難抓的地方在於:程式看起來完全正常,只有在條件不成立時才會露餡。

⚠️ 一條沒有例外的規則

永遠寫大括號。即使現在只有一行,你或別人之後加第二行時就會踩到。歷史上最有名的案例是 2014 年 Apple 的 goto fail —— 一個沒有大括號的 if 底下多了一行重複的 goto,SSL 憑證驗證整個失效。

if (x = 5) 為什麼編得過

Python 會直接給你語法錯誤,C++ 不會。理由在 PART 01 已經鋪好了:賦值本身是一個運算式,它的值就是被賦進去的那個值,而任何非零整數當條件都算真。

少打一個等號,條件永遠成立
#include <iostream> using namespace std; int main() { int x = 0; if (x = 5) { // 想寫的是 x == 5,少打一個等號 cout << "進到 if 裡面了,而且 x 已經被改成 " << x << endl; } else { cout << "沒進來" << endl; } int y = 3; if (y == 5) { cout << "y 等於 5" << endl; } else { cout << "y 不等於 5,y 還是 " << y << endl; } return 0; }
預期輸出
進到 if 裡面了,而且 x 已經被改成 5
y 不等於 5,y 還是 3

x = 5 這個運算式的值是 5,5 不是零,所以條件成立,而且 x順手改掉了。g++ 預設不會警告;加上 -Wall 才會提醒你 suggest parentheses around assignment。如果寫成 if (x = 0),條件就永遠不成立——同樣沒有任何錯誤訊息。

💡 兩個自保習慣

使用 -Wall -Wextra 可協助發現可疑賦值。條件裡出現單一 = 時,要確認自己要的是「改值」還是 == 的「比較」;編譯成功不代表意圖正確。

三元運算子 ?::一行選一個值的 if/else

cond ? a : b 是「有值的 if/else」:cond 成立整個式子就變成 a,不成立就變成 b。它做的事跟上面的 if/else 一模一樣,差別是它本身就是一個值,可以直接拿去指派、印出、或當函式的參數,不用像 if/else 那樣先宣告一個變數,兩邊分支再各指派一次。

#include <iostream> using namespace std; int main() { int total = 47; int count = 3; double average = count == 0 ? 0.0 : static_cast<double>(total) / count; cout << average << endl; int count0 = 0; double emptyAverage = count0 == 0 ? 0.0 : static_cast<double>(total) / count0; cout << emptyAverage << endl; return 0; }
預期輸出
15.6667
0

三元運算子只求值被選中的分支。count0 為 0 時會選左側的 0.0,右側除法不會執行;count 為 3 時則選右側。因為 totalcount 都是 int,範例先將 total 轉成 double,保留除法的小數部分。

寫法C++ 程式碼
完整 if/elseif (count == 0) average = 0.0;
else average = static_cast<double>(total) / count;
三元寫法average = count == 0 ? 0.0 : static_cast<double>(total) / count;

兩種寫法在這個例子中得到相同結果;三元運算式本身會產生一個值,因此適合在兩個簡單值之間選擇。若每條分支需要多個敘述,使用完整的 ifelse 會更清楚。

隨堂 · 三元運算子

int x = (5 > 3) ? 10 : 20; 之後 x 是多少?

(A) 10
(B) 20
(C) 編譯錯誤
(D) 15
PART 03 · switch

switch 與掉落:Python 沒有的控制流程

當你要拿同一個整數或字元去比對一長串固定值時,switch 比一串 else if 好讀。Python 到 3.10 才有 match,而且語意完全不同,所以這一節對你是全新的。

switch (運算式) { // 運算式必須是整數型別:int、char、enum case 常數1: 敘述; break; // 沒有 break 就會往下一個 case 掉 case 常數2: 敘述; break; default: // 都不符合時走這裡,可以省略 敘述; }

case 後面必須是編譯期就知道的常數,不能放變數、不能放範圍、不能放字串。break 的作用是「跳出這個 switch」,少寫一個,控制流就會直接掉進下一個 case 的主體。

四個 case 都忘了 break
#include <iostream> using namespace std; void grade(char g) { cout << "輸入 " << g << " -> "; switch (g) { // 每個 case 都忘了 break case 'A': cout << "很好 "; case 'B': cout << "不錯 "; case 'C': cout << "及格 "; default: cout << "(一路掉到底)"; } cout << endl; } int main() { grade('A'); grade('B'); grade('C'); grade('F'); return 0; }
預期輸出
輸入 A -> 很好 不錯 及格 (一路掉到底)
輸入 B -> 不錯 及格 (一路掉到底)
輸入 C -> 及格 (一路掉到底)
輸入 F -> (一路掉到底)

輸入 'A' 時,四段全部被執行了。switchcase 是跳進去的位置,不是一個個獨立的分支——找到相符的標籤就從那裡開始往下跑,一路跑到 break 或右大括號為止。這叫掉落(fall-through)

掉落也可以是故意的。把多個 case 疊在一起、中間什麼都不寫,就是「這幾個值走同一段程式」的標準寫法:

刻意讓多個 case 共用同一段
#include <iostream> using namespace std; void classify(char c) { cout << "'" << c << "' -> "; switch (c) { case 'a': // 故意讓 a e i o u 掉到同一段 case 'e': case 'i': case 'o': case 'u': cout << "母音" << endl; break; case '+': case '-': cout << "運算子" << endl; break; default: cout << "其他" << endl; } } int main() { classify('a'); classify('u'); classify('-'); classify('z'); return 0; }
預期輸出
'a' -> 母音
'u' -> 母音
'-' -> 運算子
'z' -> 其他

五個母音的 case 共用一個主體與 break,表示不同輸入採取相同處理。case 是進入位置,離開仍取決於 break 或其他控制流程,不能把每個 case 當成自動互斥的區塊。

switch一串 else if
比對的東西只能是整數/字元/列舉任何布林運算式
能比範圍嗎不行可以,x > 10 && x < 20
忘記寫收尾掉落,靜靜地跑錯不會,分支天生互斥
什麼時候用值是一組固定常數(選單、字元分類)其他所有情況
隨堂 1 · 掉落會掉多遠

下面這段的 switch (2) 會印出什麼?

switch (n) { // n = 2 case 1: cout << "one "; case 2: cout << "two "; case 3: cout << "three "; break; case 4: cout << "four "; }
(A) two
(B) two three
(C) two three four
(D) one two three
✅ 現代 C++ 對照

C++17 可用 [[fallthrough]]; 表達刻意接著執行下一個 case 的意圖,讓讀者及編譯器辨認。它不會中止流程;要離開 switch 仍須使用 break。

PART 04 · while 迴圈

while:不知道要跑幾圈的時候用它

while 每次先檢查條件,成立才執行主體。適合停止時機由資料決定的情境,例如讀取到 quit 才停止,或把數量逐步減到 0。特別檢查主體是否確實改變影響條件的狀態。

while (條件) { // 先檢查,再決定要不要跑 敘述; } do { // 先跑一次,再檢查 敘述; } while (條件); // 分號不能少
同一個永遠不成立的條件,兩種迴圈的差別
#include <iostream> using namespace std; int main() { int n = 10; int rounds = 0; while (n < 5) { // 條件一開始就不成立 rounds++; n++; } cout << "while 的主體跑了 " << rounds << "" << endl; int m = 10; rounds = 0; do { // 先跑一次再檢查 rounds++; m++; } while (m < 5); cout << "do-while 的主體跑了 " << rounds << "" << endl; return 0; }
預期輸出
while    的主體跑了 0 次
do-while 的主體跑了 1 次

n 一開始就是 10,n < 5 從頭到尾不成立。while 一圈都沒跑,do-while 跑了一圈才發現。do-while 的主體保證至少執行一次,所以它適合「先做一次,再問要不要繼續」,例如讀使用者輸入直到合法為止。

無窮迴圈:條件永遠成立

迴圈跑不完只有一個原因:主體沒有把條件推向不成立。下面兩種是最常見的寫法錯誤。

int i = 0; while (i < 10) { cout << i; // 錯誤示範:漏了 ++i,條件一直成立 } int remaining = 3; while (remaining > 0) { cout << remaining; // 同樣漏了 --remaining }

也可以使用 while (true),在讀到特定輸入時以 break 離開;這種結構把停止判斷放在主體中:

while (true) { // 或 for (;;) 讀一筆輸入; if (輸入 == "quit") break; 處理; }

每圈減半:更新方式決定迴圈圈數

另一種更新方式是每圈將剩餘數量除以 2。下例從 40 開始,依序變成 20、10、5、2、1、0。每圈不再只減一,所以執行次數較少;仍然是「先判斷、做主體、更新、再判斷」同一套 while 規則。

每圈減半:完整程式與圈數紀錄
#include <iostream> using namespace std; int main() { int remaining = 40; int rounds = 0; while (remaining > 0) { ++rounds; cout << "" << rounds << " 圈:remaining = " << remaining << endl; remaining /= 2; } cout << "" << rounds << " 圈,最後剩 " << remaining << endl; }
預期輸出
第 1 圈:remaining = 40
第 2 圈:remaining = 20
第 3 圈:remaining = 10
第 4 圈:remaining = 5
第 5 圈:remaining = 2
第 6 圈:remaining = 1
共 6 圈,最後剩 0

這個例子從 40 開始,每次做整數除以 2,共執行 6 圈才變成 0。一般而言,正整數每圈至少減半,圈數隨初值的二進位位數成長;這正是從更新式判讀對數成長的方式。

6
初值 40 要幾圈
10
1000 個元素要幾圈
20
一百萬個元素要幾圈
O(log n)
從條件讀出的複雜度
PART 05 · for 迴圈

三段式 for:三個位置,三個不同的執行時機

Python 的 for 是「把可迭代物件的東西一個一個拿出來」。C++ 的三段式 for 不是那個意思,它就是一個 while 的縮寫,只是把三件事排在同一行:

for (初始化; 條件; 遞增) { 主體; }

執行順序是這樣的:初始化只在最開始跑一次;接著檢查條件,成立就跑主體,跑完執行更新,再回去檢查條件。若迴圈正常因條件變成假而結束,條件檢查會比主體執行多一次;若以 breakreturn 或例外提早離開,則不能套用這個計數。

按「開始」,一步一步看三段式 for 的四個動作輪流發生。
速度 條件檢查次數:0 / 主體執行次數:0
這一行 for CODE
for (int i = 0; i < 3; i++) { cout << i << " "; }
攤開來的等價寫法 SAME
int i = 0; // 1 初始化,只跑一次 while (i < 3) { // 2 檢查條件 cout << i << " "; // 3 主體 i++; // 4 遞增 } // 回到 2

兩段程式碼完全等價。差別只在 for 把 ①②④ 收在同一行,讓讀的人一眼看見迴圈變數的生命週期。

讓程式自己報告每個動作發生的順序
#include <iostream> using namespace std; int main() { cout << "初始化 i = 0" << endl; for (int i = 0; i < 3; ++i) { cout << "條件成立,主體讀到 i = " << i << endl; } cout << "i 更新到 3,條件不成立,結束" << endl; }
預期輸出
初始化 i = 0
條件成立,主體讀到 i = 0
條件成立,主體讀到 i = 1
條件成立,主體讀到 i = 2
i 更新到 3,條件不成立,結束

把三段各換成一個會印字的函式,順序就攤開了:初始化的字只出現一次,然後是「檢查 → 主體 → 遞增」重複三輪,最後多一次落空的檢查。主體 3 次、條件 4 次——分析複雜度時我們數的是主體那一欄,但別忘了條件那一次也要跑。

寫法跑幾圈i 走過哪些值常見用途
for (int i = 0; i < n; i++)n0 … n−1走訪長度 n 的陣列,最標準的寫法
for (int i = 1; i <= n; i++)n1 … n從 1 數到 n,數學公式對照時用
for (int i = n−1; i > 0; i--)n−1n−1 … 1從末端倒著走到 1
for (int i = 0; i < n; i += 2)⌈n/2⌉0, 2, 4 …跳著走
for (int i = n; i > 0; i /= 2)⌊log₂n⌋+1n, n/2, n/4 …每圈砍一半,這是 log
⚠️ 迴圈變數的範圍

for (int i = 0; …) 的 i 只在整個 for 敘述內有效,結束後不能從外面讀它。如果要保留最終值,可在 for 前宣告 i。容器 size() 的回傳型別為無號型別,與有號索引比較要留意轉換與警告。

計時樣板:量一段迴圈跑多久都靠這四行

要觀察一段程式在目前環境的耗時,可使用 <chrono>。auto 讓編譯器從初值推導型別;steady_clock 提供適合量測經過時間的時鐘。先記起點,再以終點減起點,最後轉成秒數。

#include <chrono> using namespace std::chrono; auto start = steady_clock::now(); // …要計時的程式… double secs = duration<double>(steady_clock::now() - start).count();

三個地方值得逐行看懂:

auto:這是本頁第一次正面介紹它,意思是「型別交給編譯器自己推算,我不寫」。steady_clock::now() 回傳的型別全名是 std::chrono::time_point<std::chrono::steady_clock>,沒有人會想每次計時都手打這一長串,auto start = … 讓編譯器自己看右邊的值決定 start 的型別——這裡不是偷懶,是這個型別名字長到非用不可。② steady_clock::now():拿「現在」這一刻的時間點,呼叫兩次、中間夾著要量的程式,前後兩個時間點相減就是經過的時間。③ duration<double>(…).count():兩個時間點相減得到的是一個 duration 物件,單位不是秒;duration<double>(...) 把它轉成「以 double 表示的秒數」,.count() 再把數字取出來變成一個普通的 double

用固定次數的迴圈示範計時
#include <iostream> #include <chrono> using namespace std; int main() { for (int run = 0; run < 3; ++run) { auto start = chrono::steady_clock::now(); long long total = 0; for (int i = 1; i <= 10000; ++i) total += i * 2; double seconds = chrono::duration<double>(chrono::steady_clock::now() - start).count(); cout << "total = " << total << ", seconds = " << seconds << endl; } }
輸出說明
三次的 total 都是 100010000;seconds 是每次計算經過的秒數。

用 steady_clock::now() 記下開始與結束時間,兩者相減得到經過時間,再用 duration<double> 換算成秒。程式重複計算三次,每次都印出總和與耗時;工作很短時可能量到 0。

💡 <chrono> 要明確 include

使用 steady_clockduration 前應寫 #include <chrono>,不要依賴其他標頭間接帶入宣告。若省略,編譯器通常會回報名稱未宣告。

PART 06 · 範圍 for

for (T x : v):最像 Python 的那個 for,也最容易寫錯

💡 先備:vector<int>int&

vector<int> v = {1, 2, 3}; 建立一個可依序走訪的整數容器。int& x 表示 x 是某個既有整數的別名;經由 x 修改數值,也會改到原本的整數。下方範圍 for 正是用這兩項語法比較「複本」與「別名」。

C++11 的範圍 for 可依序走訪容器中的每個元素。以下用剛介紹的 vector<int> 保存數個整數,比較讀取副本與透過參考修改元素的不同。

寫法x 是什麼能改到容器嗎會複製嗎什麼時候用
for (T x : v)每個元素的複本不能會,每圈一份元素很小(intchar)且只是讀
for (T& x : v)每個元素的別名不會要修改元素
for (const T& x : v)唯讀的別名不能不會元素很大又只是讀,例如 string、物件
✅ 把 T 換成 auto,可由元素推導型別

上表的 T 可以換成 auto,從容器推導元素型別。for (auto x : v) 仍建立副本,for (auto& x : v) 仍建立別名,for (const auto& x : v) 仍以參考唯讀走訪。auto 省下型別名稱,範圍 for 省下逐步走訪的寫法,& 則決定是否參考原元素。完整對照見 P6:auto 與範圍 for 的語法糖

差別就是一個 &。沒有它,你改的是一份用完就丟的複本;有了它,你改的是容器裡的本尊。

一個 & 之差,改得動與改不動
#include <iostream> #include <vector> using namespace std; void show(const vector<int> &v, const char *tag) { cout << tag; for (int x : v) cout << x << " "; cout << endl; } int main() { vector<int> v = {1, 2, 3}; show(v, "原始 : "); for (int x : v) x *= 10; // x 是複本,改的是複本 show(v, "for (int x) : "); for (int &x : v) x *= 10; // x 是本尊的別名 show(v, "for (int &x) : "); return 0; }
預期輸出
原始         : 1 2 3
for (int x)  : 1 2 3
for (int &x) : 10 20 30 

中間那一圈 x *= 10 明明執行了三次,v一點都沒變——因為 x 每圈都是一份新的複本,改完就被丟掉。加上 & 之後同一行才真的動到 v。g++ 對第一種寫法連警告都不會給,這個 bug 只能靠自己看出來。

💡 一個好記的判斷順序

要修改元素時用 T&。只讀取 int 這類小型別時,直接用 T 清楚且成本低;只讀取 stringvector 或自訂大型物件時,用 const T& 避免複製。

⚠️ 為什麼複製也有成本

複製與參考的差別也會出現在函式參數:複製大型容器可能昂貴,唯讀參考可以避免整份複製。這裡先掌握 range-for 的元素別名,下一頁 P3 再從函式宣告學到參數傳遞。

PART 07 · 巢狀與跳出

數迴圈次數,就是在數複雜度

假設外層固定執行 n 次,而且每一圈內層也完整執行 n 次,內層主體共 n × n 次。若內層上限隨外層變動,就逐圈加總。先數這些具體次數,再談成長速度;迴圈形狀本身不能取代對每圈工作量的檢查。

方形與三角形:兩種雙層迴圈的次數
#include <iostream> using namespace std; int main() { int n = 8; long square = 0; for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) square++; cout << "方形雙層 n = " << n << ":內層跑 " << square << " 次,等於 n * n" << endl; long tri = 0; for (int i = n - 1; i > 0; i--) // 第 i 輪計算 i 組配對 for (int j = 0; j < i; j++) tri++; cout << "逐輪減少的雙層 n = " << n << ":內層跑 " << tri << " 次,等於 n(n-1)/2" << endl; cout << "n 變兩倍,比較次數大約變四倍:" << endl; for (int k = 8; k <= 128; k *= 2) { long t = (long)k * (k - 1) / 2; cout << " n = " << k << " -> " << t << "" << endl; } return 0; }
預期輸出
方形雙層 n = 8:內層跑 64 次,等於 n * n
逐輪減少的雙層 n = 8:內層跑 28 次,等於 n(n-1)/2
n 變兩倍,比較次數大約變四倍:
  n = 8 -> 28 次
  n = 16 -> 120 次
  n = 32 -> 496 次
  n = 64 -> 2016 次
  n = 128 -> 8128 次

完整方形共 n × n = 64 次;三角形版本的內層上限隨外層縮短,共 n(n−1)/2 = 28 次,少於方形的一半。兩者在 n 加倍時都約成長四倍;假設每次主體工作量固定,便都屬於 O(n²)。

外層次數 n 6
內層次數 m 6
拉動滑桿看內層主體被執行幾次。
現在在數的迴圈 CODE
次數 COUNT
36
內層主體執行次數
O(n·m)
複雜度

breakcontinue:提早離開的兩種方式

break 是「這個迴圈我不跑了」,continue 是「這一圈剩下的不做了,直接進下一圈」。兩個都只作用在最靠近的那一層迴圈

break 只跳出一層
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v = {17, 93, 26, 54, 77}; int target = 26; for (unsigned i = 0; i < v.size(); i++) { if (v[i] % 2 != 0) { cout << " " << v[i] << " 是奇數,continue 跳過剩下的主體" << endl; continue; } cout << " 檢查 " << v[i] << endl; if (v[i] == target) { cout << " 找到了,break 離開迴圈" << endl; break; } } cout << "break 只跳出「最內層」那一圈:" << endl; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (j == 1) break; // 只中斷 j 這一層 cout << " i = " << i << ",j = " << j << endl; } } return 0; }
預期輸出
  17 是奇數,continue 跳過剩下的主體
  93 是奇數,continue 跳過剩下的主體
  檢查 26
  找到了,break 離開迴圈
break 只跳出「最內層」那一圈:
  i = 0,j = 0
  i = 1,j = 0
  i = 2,j = 0

第二段內層在 j == 1 時 break,只離開當前內層迴圈,外層仍執行三次。要離開多層可用額外旗標,在每層檢查是否停止;學過函式後也可用 return 離開整個函式。

迴圈形狀次數複雜度使用情境
for i < nnO(n)依序處理 n 筆資料
for i < n { for j < n }O(n²)走訪二維陣列
for i = n−1 … { for j < i }n(n−1)/2O(n²)逐輪少做一次的配對工作
while (範圍砍一半)log₂nO(log n)二分搜尋
外層 n 次 { 內層每次減半 }n log₂nO(n log n)重複執行減半流程
EX · 隨堂練習

把三個常見錯誤合在一題裡

EXERCISE · 這段迴圈跑完之後,v 是什麼

下面這段程式印出什麼?

vector<int> v = {2, 4, 6}; for (int x : v) { if (x = 0) continue; // 注意這裡只有一個等號 x *= 3; } for (int y : v) cout << y << " ";
(A) 6 12 18
(B) 2 4 6
(C) 0 0 0
(D) 編譯錯誤
🎯 想自己再練,就改這三行

把上面那段貼進 C++ Tutor,然後做三個改動:① 把 int x 改成 int &x;② 把 x = 0 改成 x == 0;③ 兩個一起改。三次的輸出都不一樣,能講出為什麼,這一頁就讀完了。

REFERENCE · 速查表

P2 速查表

語法骨架

if (條件) { … } else if (條件) { … } else { … } switch (整數運算式) { case 常數: … break; // 少了 break 就會掉到下一個 case default: … } while (條件) { … } // 先檢查,可能一次都不跑 do { … } while (條件); // 先跑一次,再檢查;分號不能少 for (初始化; 條件; 遞增) { … } // 初始化只跑一次;條件比主體多跑一次 for (T x : v) { … } // 複本,改不到 v for (T& x : v) { … } // 別名,改得到 v for (const T& x : v) { … } // 唯讀別名,不複製 break; // 跳出最內層的迴圈或 switch continue; // 這一圈剩下的不做,直接進下一圈

三種迴圈怎麼選

while三段式 for範圍 for
什麼時候用不知道要跑幾圈要用到索引 i只是把元素一個個拿出來
主體保證跑一次嗎不保證(do-while 保證)不保證容器空的話一次都不跑
最常忘記什麼更新條件變數邊界寫成 <= 而越界那個 &
適合情境重複到輸入有效或剩餘量歸零固定次數與索引走訪逐一讀取容器元素

Python ↔ C++ 對照

你在 Python 寫的C++ 對應差在哪
if x > 0:if (x > 0) { … }條件要小括號,主體要大括號
elifelse ifC++ 是兩個字,中間有空格
and / or / not&& / || / !兩邊都有短路求值
match / caseswitch / caseC++ 會掉落,要自己寫 break
while cond:while (cond) { … }一樣,只差括號
沒有 do-whiledo { … } while (cond);主體保證至少跑一次
for i in range(n):for (int i = 0; i < n; i++)C++ 把三段攤開給你控制
for x in v:for (int x : v)C++ 預設是複本,要改要加 &
for i, x in enumerate(v):for (unsigned i = 0; i < v.size(); i++)C++ 沒有內建的 enumerate
break / continuebreak / continue一樣,都只作用在最內層
for … else沒有對應用旗標變數自己做

五條記得住的規則

① 條件裡「先檢查指標、再解參考」,順序不能顛倒,靠的是 && 的短路求值。
② 永遠寫大括號,即使主體只有一行。
③ 每個 case 寫完就問一次:這裡要不要 break
④ 迴圈寫完就問一次:主體有沒有把條件推向不成立。
⑤ 範圍 for 要改元素就加 &,只讀又是大東西就寫 const T&

延伸閱讀

資源看什麼
C++ Tutor把本頁每一段迴圈貼進去單步執行,看變數怎麼變、條件什麼時候被檢查。
cppreference · range-for範圍 for 展開之後的真正樣子,以及為什麼 & 會改變行為。
cppreference · switch掉落的完整規則,以及 C++17 的 [[fallthrough]]
cppreference · for statement三段式與範圍 for 的正式語法參考。
本站 · 演算法分析延伸本頁 PART 07,正式介紹漸近複雜度與成長率。
cppreference · break statementbreak 在迴圈與 switch 中的正式語法與作用範圍。
QUIZ · 自我檢測

自我檢測:流程控制 隨堂自測 · 6 題

每個選項都有解說:選錯也點開看看為什麼錯。全對之後再往下翻詞彙卡。

Q1.這段程式印出什麼?
#include <iostream>
using namespace std;

int main() {
    int sum = 0;
    for (int i = 1; i <= 4; i++) {
        sum += i;
    }
    cout << sum << endl;
    return 0;
}
Q2.switch 少了 break,這段印出什麼?
#include <iostream>
using namespace std;

int main() {
    int n = 2;
    switch (n) {
        case 1: cout << "one ";
        case 2: cout << "two ";
        case 3: cout << "three ";
                break;
        case 4: cout << "four ";
    }
    cout << endl;
    return 0;
}
Q3.兩個範圍 for 只差一個 & 符號,最後印出什麼?
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3};
    for (int x : v) x += 10;
    for (int &y : v) y += 100;
    for (int z : v) cout << z << " ";
    cout << endl;
    return 0;
}
Q4.條件一開始就不成立,這段的 count 是多少?
#include <iostream>
using namespace std;

int main() {
    int i = 5;
    int count = 0;
    do {
        count++;
        i++;
    } while (i < 3);
    cout << count << endl;
    return 0;
}
Q5.這個三角形雙層迴圈,內層主體執行幾次?
#include <iostream>
using namespace std;

int main() {
    int hits = 0;
    for (int i = 0; i < 5; i++) {
        for (int j = i + 1; j < 5; j++) {
            hits++;
        }
    }
    cout << hits << endl;
    return 0;
}
Q6.條件 divisor != 0 && numerator / divisor > 3 可以避免除以零。如果把 && 兩邊對調,會發生什麼?
CARDS · 關鍵詞彙卡

關鍵詞彙卡:點卡片翻面

先看正面術語,心中默想定義再翻面對答案;洗牌後再過一輪,直到每張都能不假思索說出來。