# [APCS] 流程控制
###### tags: `APCS`
結構化程式設計(structured programming)包含三種流程控制結構:
* 循序結構(sequential structure)
* 選擇結構(selection structure)
* 迴圈結構(loop structure)
其中,最基本的循序結構就是一句一句程式碼依序執行下去的結構。接下來我們分別介紹選擇結構和迴圈結構。
## 選擇結構
選擇結構是一種由條件所控制的敘述,其中包含了至少一個條件判斷式,如果條件為真,則執行某些程式區塊。
選擇結構必須配合邏輯判斷式來建立條件指令。C和C++有四種選擇結構:
* `if`
`if`的語法如下:
```cpp=
if (條件判斷式){
判斷式為真時要執行的指令...
}
```
其流程圖如下:

當條件運算式成立(也就是估算值為`true`)時,程式將執行大括號內的指令;條件運算式不成立(估算值為`false`)時,則不執行指令並結束這個敘述式。
注意當括號內的指令只有一行時,可以不包上括號,如下:
```cpp=
if (條件運算式)
判斷為真時要執行的指令;
```
* `if-else`
`if-else`的語法如下:
```cpp=
if (條件運算式){
判斷為真時要執行的指令;
}
else{
判斷不為真時要執行的指令;
}
```
流程圖如下:

當我們的判斷條件比較複雜的時候,可能會出現**巢狀**的`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("不買");
}
```
這種做法是一種多選一的條件指令,程式會依序判讀每一個條件是否符合,並選擇第一個成立的程式區塊去做執行。

* 條件運算子
條件運算子是一種三元運算子,可以替代簡單的`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`迴圈。

* 後測試型迴圈
結束條件寫在程式區塊後面。所以至少會執行一次迴圈內的敘述,再測試條件是否成立,若成立則會返回區塊的一開始,重複執行迴圈。例如`do-while`迴圈。

使用迴圈時,必須確保條件最終會變為假,否則迴圈會一直執行下去,進入無限迴圈。這可能會導致程序的當機或效能下降,因此要謹慎使用迴圈,並仔細檢查條件的邏輯。
無論是哪種迴圈,都有兩個最基本的條件:
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`
:::