# [APCS] 流程控制 ###### tags: `APCS` 結構化程式設計(structured programming)包含三種流程控制結構: * 循序結構(sequential structure) * 選擇結構(selection structure) * 迴圈結構(loop structure) 其中,最基本的循序結構就是一句一句程式碼依序執行下去的結構。接下來我們分別介紹選擇結構和迴圈結構。 ## 選擇結構 選擇結構是一種由條件所控制的敘述,其中包含了至少一個條件判斷式,如果條件為真,則執行某些程式區塊。 選擇結構必須配合邏輯判斷式來建立條件指令。C和C++有四種選擇結構: * `if` `if`的語法如下: ```cpp= if (條件判斷式){ 判斷式為真時要執行的指令... } ``` 其流程圖如下: ![](https://hackmd.io/_uploads/rkTLBuJCj.png) 當條件運算式成立(也就是估算值為`true`)時,程式將執行大括號內的指令;條件運算式不成立(估算值為`false`)時,則不執行指令並結束這個敘述式。 注意當括號內的指令只有一行時,可以不包上括號,如下: ```cpp= if (條件運算式) 判斷為真時要執行的指令; ``` * `if-else` `if-else`的語法如下: ```cpp= if (條件運算式){ 判斷為真時要執行的指令; } else{ 判斷不為真時要執行的指令; } ``` 流程圖如下: ![](https://hackmd.io/_uploads/SkwwSO10o.png) 當我們的判斷條件比較複雜的時候,可能會出現**巢狀**的`if-else`結構: ```cpp= if (price < 200){ printf("買吧"); } else{ if (price < 400){ printf("去問你媽"); } else{ printf("不買"); } } ``` 但是我們不鼓勵使用大量的巢狀結構,比較清晰的作法是寫成以下形式: ```cpp= if (price < 200){ printf("買吧"); } else if (price < 400){ printf("去問你媽"); } else{ printf("不買"); } ``` 這種做法是一種多選一的條件指令,程式會依序判讀每一個條件是否符合,並選擇第一個成立的程式區塊去做執行。 ![](https://hackmd.io/_uploads/B1mZ2O1Ri.png) * 條件運算子 條件運算子是一種三元運算子,可以替代簡單的`if-else`指令(但指令只允許單行),使用方法如下: ```cpp= 條件運算式 ? 判斷為真時要執行的指令 : 判斷為假時要執行的指令; ``` 這相當於: ```cpp= if (條件運算式){ 判斷為真時要執行的指令; } else{ 判斷為假時要執行的指令; } ``` 你也可以把條件運算子用在指定值給一個變數的時候: ```cpp= int a = (c > b) ? 0 : 100; ``` 這相當於: ```cpp= int a; if (c > b){ a = 0; } else{ a = 100; } ``` 也相當於在Python中的 ```python= a = 0 if c > b else 100 ``` * `switch-case` `switch-case`也是一種多選一的條件指令,可用於根據不同的條件值執行不同的代碼塊。語法如下: ```cpp= switch (expression){ case value1: 如果 expression 等於 value1 要執行的指令; break; case value2: 如果 expression 等於 value2 要執行的指令; break; // 可以有多個 case default: 如果 expression 不等於任何一個 case 要執行的指令; break; } ``` 當`switch`後面的`expression`與其中一個`case`的值相等時,該`case`下的敘述將被執行。如果沒有任何一個`case`的值與`expression` 等,則會執行`default`下的敘述。注意這邊每個`case`後面都要加上`break`來表示跳出`switch`結構,否則將繼續執行下一個`case`下的敘述,而不管其條件是否滿足。有時候可以利用這個性質,但這種作法可能會讓程式碼難以閱讀。 ## 迴圈結構 迴圈(loop)會重複執行一個程式區塊的程式碼,直到符合特定的終止條件為止。依照結束條件判斷的時機,分為兩種: * 前測試型迴圈 結束條件寫在程式區塊的前面。當條件成立,才執行區塊內的敘述。例如`for`和`while`迴圈。 ![](https://hackmd.io/_uploads/Sy_u1t1Ai.png) * 後測試型迴圈 結束條件寫在程式區塊後面。所以至少會執行一次迴圈內的敘述,再測試條件是否成立,若成立則會返回區塊的一開始,重複執行迴圈。例如`do-while`迴圈。 ![](https://hackmd.io/_uploads/BJHtJtk0j.png) 使用迴圈時,必須確保條件最終會變為假,否則迴圈會一直執行下去,進入無限迴圈。這可能會導致程序的當機或效能下降,因此要謹慎使用迴圈,並仔細檢查條件的邏輯。 無論是哪種迴圈,都有兩個最基本的條件: 1. 迴圈的執行主體:由程式指令區塊組成 2. 迴圈的條件判斷:用於決定迴圈何時結束 ### `for`迴圈 `for`迴圈是迴圈結構中最常見的形式,可以用來執行指定的次數。在使用`for`迴圈的時候,必須給定迴圈**控制變數的初始值**、執行迴圈的**條件運算式**以及控制變數**走一步需要增減的值**,語法如下: ```cpp= for (控制變數的初始值; 條件運算式; 走一步需要增減的值){ 要執行的指令; } ``` 例如下列程式,使用`for`迴圈來計算1累加到10的值: ```cpp= int i, sum; for (i = 0; i <= 10; i++){ sum += i; } printf("1累加到10為%d\n", sum); ``` 你當然也可以嵌套多層的`for`迴圈,程式會先迭代完內層迴圈,才繼續迭代外層迴圈。例如以下程式碼會輸出九九乘法表: ```cpp= for (int i = 1; i < 10; i++){ printf("==== %d ====\n", i); for (int j = 1; j < 10; j++){ printf("%d x %d = %d\n", i, j, i * j); } } ``` 注意雖然`for`迴圈有很大的自由度,但務必注意要設定好迴圈終止的條件,以免產生無窮迴圈。但無窮迴圈也不是完全沒有用處,例如有時候我們希望系統持續監控一項活動、或是持續執行一段指令的時候就會用得到。 ### `while`迴圈 當我們沒辦法確定要執行迴圈的確切次數、只知道迴圈的終止條件時,就是使用`while`迴圈的時機了。`while`迴圈的語法如下: ```cpp= while (條件運算式){ 要執行的指令; } ``` 當條件運算式被判斷為`true`的時候,就會執行下面的指令區塊。例如下面的程式碼就是一個會不斷印出hello的無窮迴圈: ```cpp= while (1){ printf("hello\n"); } ``` ### `do-while`迴圈 `do-while`迴圈和`while`迴圈類似,都包含一個條件運算式,當條件運算式被判斷為`true`時才會執行指令。差別在於,`do-while`迴圈是在執行完指令時判斷是否需要進行下一次迭代,因此至少會執行指令一次。`do-while`迴圈語法如下: ```cpp= do { 要執行的指令; } while (條件運算式); ``` ## 流程控制指令 對於一個用基本流程控制寫出的結構化設計程式,有時候使用者會想要一些「例外」,例如必須中斷或讓迴圈提前結束等。這時可以使用的指令包括了`break`、`continue`等。你也可以使用`goto`指令來改變至任何想要的位置,但一般不建議這麼做,因為這樣會讓可讀性降低,甚至成為不可維護的「麵條代碼」。 ### `break` `break`代表的是中斷的意思。它的功能是可以跳離最近一層的`for`、`while`、`do-while`和`switch`的程式碼區塊,並將控制權交給所在區塊外的下一行程式。注意當遇到巢狀迴圈時,`break`只會跳離最近一層的迴圈。 例如以下範例可以印出1到5: ```cpp= int i = 1; while (1){ if (i > 5){ break; } printf("%d\n", i); i++; } ``` `break`常常在無限迴圈中出現,作為迴圈的終止。 ### `continue` 在迴圈中遇到`continue`指令時,會直接跳過該次迭代,而回到迴圈的一開始處,進行下一次迭代。`continue`和`break`最大的差別在於,`continue`只是忽略該次迭代後面尚未執行的指令,但並未跳離該迴圈。 以下範例可以印出1到100所有不能被5整除的數: ```cpp= for (int i = 1; i <= 100; i++){ if (i % 5 == 0){ continue; } printf("%d\n", i); } ``` ## 例題 * 以下程式碼的功能為:輸入六個整數,並印出最後一個數字是否為最小的值。但這段程式是錯誤的。請問哪一組測資可以檢測出程式有誤? ```cpp= #define TRUE 1 #define FALSE 0 int d[6], val, all_big; for (int i = 1; i <= 5; i++){ scanf("%d", &d[i]); } scanf("%d", &val); all_big = TRUE; for (int i = 1; i <= 5; i++){ if (d[i] > val){ all_big = TRUE; } else{ all_big = FALSE; } } if (all_big == TRUE){ printf("%d is the smallest.\n", val); } else{ printf("%d is not the smallest.\n", val)); } ``` (A) 11 12 13 14 15 3 (B) 11 12 13 14 25 20 \(C\) 23 15 18 20 11 12 (D) 18 17 19 24 15 16 :::spoiler 解答 (B) ::: * 以下程式: ```cpp= for (int i = 0; i <= 3; i++){ for (int j = 0; j < i; j++){ printf(" "); } for (int k = 6 - 2 * i; ______ ; k--){ printf("*"); } printf("\n"); } ``` 若要印出下列圖案: ``` ****** **** ** ``` 則`______`內要如何設定? :::spoiler 解答 `k > 0` ::: * 以下程式無法正確列印20次的hello,下列修正方法何者仍然無法正確列印20次? ```cpp= for (int i = 0; i <= 100; i = i + 5){ printf("hello\n"); } ``` (A) 將`i <= 100`和`i = i + 5`分別改為`i < 20`和`i = i + 1` (B) 將`i = 0` 改為 `i = 5` \(C\) 將`i <= 100`改為`i < 100` (D) 將`i = 0`和`i <= 100`分別改為`i = 5`和`i < 100` :::spoiler 解答 (D) ::: * 給定以下函式: ```cpp= int f(int a){ if (______){ return a * 2 + 3; } else{ return a * 3 + 1; } } ``` 已知`f(7) = 17`,`f(8) = 25`,則`______`可為: (A) `a % 2 != 1` (B) `a * 2 > 16` \(C\) `a + 3 < 12` (D) `a * a < 50` :::spoiler 解答 (D) ::: * 考慮以下程式: ```cpp= for (int i = 0; i < 8; i++){ for (int j = 0; ______; j++){ cout << (i + j) % 2 << ' '; } cout << '\n'; } ``` 若希望輸出以下圖形: ``` 0 1 0 0 1 0 1 0 1 0 0 1 0 1 0 1 0 1 0 1 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 ``` 則`______`要填入: (A) `j <= i` (B) `j < i` \(C\) `j >= i` (D) `j > i` * 以下程式的輸出結果為何? ```cpp= int a = 2; switch (a){ case 1 + 1: cout << "A"; case 1 + 2: cout << "B"; break; default: cout << "C"; } ``` :::spoiler 解答 `AB` ::: * 以下程式無法正確輸出 $a^p$,要如何修改? ```cpp= int no = 1; while (p >= 0){ no = no * a; p = p - 1; } cout << no << '\n'; ``` :::spoiler 解答 把第2行的`p >= 0`改成`p > 0` :::