Learning Objectives
- 用 if、else if、else 依條件選擇要執行的程式,並看懂巢狀 if 與條件運算子 ?:
- 用 switch 比對整數或字元,說明 break 的作用與 fall-through
- 依情況選用 for、while、do-while 迴圈
- 用 break、continue 控制迴圈,追蹤巢狀迴圈的執行順序
- 辨認常見錯誤:= 與 == 混用、if 後面多一個分號、無窮迴圈
程式不只從上往下跑
到目前為止的程式都是一行接一行、從上往下執行。真正的程式需要做選擇(分數夠高才印「及格」)和重複(把 1 到 100 加起來), 這兩件事合稱流程控制(control flow):
| 需求 | 用什麼 |
|---|---|
| 條件成立才做 | if、if … else、else if |
| 依某個值分成好幾種情況 | switch |
| 知道要重複幾次 | for |
| 不知道次數,條件成立就繼續 | while |
| 至少要做一次 | do … while |
本課每個範例都可以逐步播放。注意左邊的箭頭:條件不成立時,箭頭會跳過那一段;迴圈每跑完一輪,箭頭會跳回迴圈開頭。
if、else if、else
if (條件) {
// 條件為 true 時執行
} else if (另一個條件) {
// 前面的條件都不成立、這個條件成立時執行
} else {
// 以上都不成立時執行
}
- 條件放在小括號裡,結果是
true或false。整數也可以當條件:0 是 false,其他值都是 true - 一串
if … else if … else由上往下檢查,只會執行第一個成立的那一段,後面的就不再檢查 - 大括號裡只有一行時可以省略大括號,但建議一律寫上,之後加程式碼時比較不會出錯
if裡面還可以再放if,稱為巢狀 if;else屬於離它最近、還沒有配對的if
條件運算子 條件 ? 值1 : 值2:條件成立時得到值1,否則得到值2。適合「二選一指定給變數」這種短的情況:
string label = (score >= 60) ? "及格" : "不及格";
成績分級:if、巢狀 if 與 ?:
score 是 72。看箭頭停在哪些 if 與 else if:前兩個條件不成立,第三個成立之後,else 那一段就直接跳過。想一想:把 score 改成 85,箭頭會怎麼走?
switch:依一個值分成好幾種情況
switch (運算式) {
case 常數1:
// ...
break;
case 常數2:
// ...
break;
default:
// 都不符合時
}
switch的運算式要是整數類的值(int、char、enum等),不能是double或string- 每個
case後面是一個常數,不能寫範圍或變數;要比對範圍(例如 80~89 分)請用if - 程式跳到符合的
case之後,會一直往下執行,直到遇到break或switch結束。忘了寫break,就會連下一個case的程式也一起執行, 這叫做 fall-through - 刻意讓幾個
case共用一段程式時,把它們連著寫(case 'B': case 'C':),這是 fall-through 的正當用法 default是「以上都不符合」,可以省略
C++17 加入了
[[fallthrough]];標記,寫在 case 的最後,表示「這裡是刻意不寫 break」。 g++ 加上-Wextra編譯時會對沒有標記的 fall-through 提出警告,有標記就不會。本站的逐步執行器目前不支援這個標記,所以範例裡沒有使用。
switch、break 與 fall-through
箭頭從 switch 那一行直接跳到符合的 case 底下。第一段 grade 是 'B',和 'C' 共用同一段程式;最後一段 level 是 2,因為沒有 break,從「第二關」一路印到「第三關」。
想一想:if 與 switch
下面的程式會印出什麼?
int x = 15;
if (x > 10) {
cout << "A";
} else if (x > 5) {
cout << "B";
} else {
cout << "C";
}
下面的程式會印出什麼?
int n = 1;
switch (n) {
case 1: cout << "1";
case 2: cout << "2"; break;
case 3: cout << "3";
}
下面的程式會印出什麼?
int a = 7;
cout << (a % 2 == 0 ? "even" : "odd");
三種迴圈
for (初始化; 條件; 更新) { ... } // 先初始化一次,每輪先檢查條件,做完再更新
while (條件) { ... } // 每輪先檢查條件
do { ... } while (條件); // 先做一次,再檢查條件(結尾要分號)
| 迴圈 | 什麼時候用 | 最少執行幾次 |
|---|---|---|
for |
次數已知,例如「從 1 數到 n」 | 0 次 |
while |
次數不知道,例如「一直除以 10 直到變成 0」 | 0 次 |
do-while |
至少要先做一次,例如「先顯示選單,再問要不要繼續」 | 1 次 |
for (int i = 1; i <= 5; i++) 裡宣告的 i 只在這個迴圈裡有效,迴圈結束後就消失了。在動畫的變數區可以看到 i 在迴圈結束後不見。
for、while、do-while
注意三件事:for 每一輪箭頭都會回到第 7 行(更新 i、再檢查條件);while 回到第 14 行;do-while 的條件 k < 3 一開始就不成立,但因為先做再檢查,還是執行了一次。
break、continue 與巢狀迴圈
break:立刻離開最內層的迴圈(或 switch),接著執行迴圈後面的程式continue:跳過這一輪剩下的程式,直接進入下一輪;在for迴圈裡會先執行更新(i++)再檢查條件
迴圈裡面可以再放迴圈,稱為巢狀迴圈。外層每跑一輪,內層就從頭跑完一整輪。印表格、走訪二維資料都會用到。
注意 break 與 continue 只影響它所在的那一層迴圈。
break、continue 與巢狀迴圈
第一段:偶數時箭頭停在第 8 行的 continue,接著直接回到第 6 行;i 是 9 時遇到 break,迴圈提早結束,10 沒有被檢查。第二段:看 row 與 col 的變化,內層迴圈跑的次數等於 row。
常見錯誤
下面三種錯誤都能通過編譯,程式也能執行,只是結果不對,所以特別難發現:
| 錯誤 | 寫成 | 實際發生的事 |
|---|---|---|
= 與 == 混用 |
if (stock = 0) |
把 0 指定給 stock,條件的值是 0,永遠不成立 |
if 後面多一個分號 |
if (temp > 30); |
分號本身是一個「空敘述」,if 只管它;下面的大括號每次都會執行 |
| 迴圈變數沒有更新 | while (count < 3) { ... } 裡忘了 count++ |
條件永遠成立,變成無窮迴圈,程式不會結束 |
g++ 加上 -Wall 編譯時,會對「在條件裡使用指定運算」提出警告,建議你加括號表明是刻意的。養成用 -Wall 編譯的習慣,可以抓到很多這類錯誤。
程式陷入無窮迴圈時,在終端機按 Ctrl + C 可以強制結束。
看起來對、其實錯的程式
注意第 7 行執行之後,stock 從 5 變成 0,而且「缺貨」沒有印出來。第 13 行的條件不成立,第 15 行卻還是執行了。
想一想:迴圈與常見錯誤
下面的程式會印出什麼?
int i = 10;
do {
cout << i;
i++;
} while (i < 5);
下面的程式會印出什麼?
for (int i = 0; i < 10; i++) {
if (i == 3) continue;
if (i == 6) break;
cout << i;
}
下面的程式會印出什麼?
int x = 0;
if (x = 5) {
cout << "yes" << x;
} else {
cout << "no" << x;
}
動手寫:FizzBuzz
讀入一個正整數 n,從 1 數到 n,每個數字印一行:3 的倍數印 Fizz、5 的倍數印 Buzz、同時是 3 和 5 的倍數印 FizzBuzz,其他印數字本身。輸入是 15。(執行時輸入的 15 會先顯示在輸出裡,就像在終端機上打字一樣。)
重點回顧
if … else if … else由上往下檢查,只執行第一個成立的那一段;條件 ? 值1 : 值2適合短的二選一switch比對整數類的值與常數;每個case結尾記得break,否則會 fall-through 到下一個casefor用在次數已知,while先檢查再執行,do-while至少執行一次break離開最內層迴圈,continue跳過這一輪剩下的程式- 巢狀迴圈:外層每跑一輪,內層跑完一整輪
- 小心
=與==、if後面的分號、忘了更新迴圈變數;用g++ -Wall編譯可以幫你抓到一部分錯誤