控制流程

**控制流程**喺電腦科學上係指電腦程式當中嘅個體陳述式（或者相等嘅嘢）同子程式等以乜嘢次序執行：喺最簡單嘅情況之下，部電腦會將個程式逐句逐句執行；但現實嘅電腦程式好少可會咁簡單，所以常用嘅程式語言冚唪唥都會有控制流程嘅語法俾用家操控例如某拃陳述式喺咩情況下會行，或者嗰拃陳述式咩時候要重複執行呀噉。

控制流程係現代程式編寫上不可或缺嘅一部份。有好多常用嘅功能都係要用控制流程先會做到嘅。舉個例說明，電子遊戲嘅程式要喺每個時間點更新個遊戲世界嘅狀態，而喺一般嘅動作遊戲當中，如果某個敵人嘅生命值變咗 0，嗰個敵人就死，跟住個程式要將佢由個遊戲世界移走，用虛擬碼可以寫成：

  **if** (單位嘅生命值 ≤ 0) 
    當個單位死咗;
    將單位由遊戲世界嗰度清除;

呢段碼會令到個程式「如果達到呢個條件，先行呢幾行碼」－令個程式唔係簡單噉逐句逐句行嗮佢，所以係控制流程技術。

控制流程可以用控制流程圖嚟表達，附圖就係一個例子。控制流程圖會用一個個**節點**，每個節點代表一段一次過要做晒嘅嘢，而節點之間會有箭咀嘅線代表嗰個位有可能會跳去箭咀指住嘅位。喺電腦科學上做編程上嘅研究嗰陣，控制流程圖經常用嚟清楚噉表達程式。

## 基本概念

控制流程大致上可以分做幾類，以下呢五類喺多數嘅主流程式語言當中都可以搵到：
*由一個陳述式無條件噉跳去第個嗰度（分支）；
*淨係喺某啲條件達到嗰時先執行某柞陳述式（條件運算式同開關運算式）；
*一路行某柞陳述式，直到某個條件達到為止（迴圈）；
*執行一柞喺好遠嘅陳述式，跟住再返去而家呢個位（子程式）；
*未執行完個程式最後嗰句陳述式，照樣終止程式（無條件終止）。

### 標記

喺電腦科學上，標記係源碼某一個位置俾嘅名或者號碼（除此之外冇任何功能）。段源碼第啲部份嘅控制流程機能可能會靠一個位置嘅標記嚟跳去呢個位置，例如 goto（睇下面）就會噉做。

程式行號係好多程式語言（包括 Python 同 MATLAB 等）都有嘅一種標記，指段源碼每行前面都有返個自然數，呢啲數字一般都會負責指明嗰行係段源碼入面嘅第幾行。不過都有啲程式語言唔使啲行號連續，淨係需要啲行號係愈落個數愈大就得，好似係以下呢段 BASIC 碼噉，第一行碼嘅行號係「10」，而第二行碼嘅行號係「20」：


```qbasic
10 LET X = 3
20 PRINT X
```


喺某啲程式語言裏面，標記會係可以用自然語言寫成嘅識別碼（`identifier`），例如 C 程式語言或者 Ada 就係噉。呢啲語言嘅做法係識別碼會喺嗰行碼嘅起始嗰度出現，跟住有個冒號，冒號後面掕住嗰行碼。例如喺 C 入面：


```c
Success: printf("The operation was successful.\n");
```


### Goto

Goto（源自英文 **go to**，**走去**嘅意思）係最原始嗰種控制流程。通常用法係放 goto 喺標記（或者行號）前面，意思係叫部電腦喺嚟到呢一行嗰陣，直接無條件噉跳去個標記（或者行號）所標示嗰個位置，再行標記（或者行號）嗰行嘅碼，當中 goto 使唔使大細階就視乎程式語言而定：

    **goto** *label*

喺組合語言等低級程式語言裏面，對應 goto 嘅 jump 或者 branch 基本上係控制流程嘅唯一方法。但係目前喺實際嘅高級程式語言裏面，goto 到咗今下經已唔多常用，啲人好少可用 goto（睇埋下面結構化程式定理），其中一個原因係意思唔清楚。另外一樣嘢係如果喺一隻用行號但係冇自動重編行號功能嘅程式語言入面用 goto，而打後個編程者加咗幾段碼嘅話，就會搞到要人手編過哂啲行號，再人手逐個 goto 執返好佢，變咗會好唔方便。

### 子程式

喺編程上，子程式（）係指一串包裝埋一齊嘅指令，通常用嚟做某樣特定嘅工作：一個子程序會包含若干句陳述式，當段源碼入面有一行講明要用嗰個子程序嗰陣，個程式就會行個子程序－子程序可以用嚟界定個程式要做多次嘅工作，等個程式員唔使吓吓都成段碼複製一次貼落去要用嘅位嗰度，例如係以下呢段虛擬碼，就會令個程式行子程式 a 三次，等個程式員唔使將子程式 a 段碼寫三次，可以慳返啲位：


```c
子程式 a
    （講好個子程式包含乜陳述式）

  行子程式 a
  行子程式 a
  行子程式 a
```


### 結構化編程

結構化編程係源自 1950 年代嘅編程範式，旨在運用各種嘅控制流程（同用呢啲控制流程取代 goto）嚟令電腦程式更加清晰同高品質，以及減少程式製作所需嘅時間。舉個例說明，如果冇咗子程式嘅使用，頭先個程式就會變成：


```c
子程式 a 嗰柞陳述式
  子程式 a 嗰柞陳述式
  子程式 a 嗰柞陳述式
```


呢種做法有多種唔好處：個編程員喺編程嗰陣要重複將同一段碼寫幾次；如果佢想改子程式 a 入面嘅陳述式，佢就要改成三次（而用咗子程式做法嘅情況淨係需要改一次）；喺現實嘅程式製作當中，一個程序閒閒地可以需要重用十次以上，所以子程式嘅使用慳咗好多時間精神。再普遍啲講，對控制流程嘅運用－結構化編程嘅諗頭－幫編程員慳咗好多時間精神。

結構化程式定理係喺 1966 年出嘅定理，革新咗程式編寫呢個領域。條定理如下：是但搵個用咗 goto 嘅程式，呢個程式都可以轉化做一個冇任何 goto 喺入面、而且功能上完全一樣嘅程式，後者當中只係需要有條件運算式同迴圈。打後仲有學者指出，查實就連條件運算式都唔係必要嘅。而呢條定理表示咗，任何一個（提出當時相對普遍嘅）有 goto 嘅程式都可以變做冇 goto 而唔喪失功能－為結構化編程奠咗基。

結構化程式定理一個簡化版嘅證明如下：想像有一段用咗若干個 goto 嘅碼，而佢啲 goto 冚唪唥都指咗向正確位置。當 S_i 做第 i 句陳述式，整一個變數 l 代表現時位置，將成段碼搵個 while 迴圈包住：


```c
while l <= M // 當中 M 係段碼裏面嘅陳述式數量
    do 個程式
```


然後再跟以下規則改寫段碼：
#將所有「S_i」改做「**if (l == i) then do** S_i**, l ← l + 1**」；
#將所有「S_i：L_i **goto** L_j」改做「**if (l = i) then l ← j - 1**」（「**a ← b**」喺編程上係指「將 a 嘅數值設成 b」）；
#將所有「S_i：**if (cond) then goto** L_j」改做「**if ((l == i) AND (cond == true)) then l ← j else l ← l + 1**」；

做咗呢三個步驟之後，得出嗰段碼喺功能上會同原本嗰段一樣，但冇嗮啲 goto －變咗一段更加易用又不失原有功能嘅源碼。呢條定理嘅證明令到編程呢個領域嘅人放棄咗「一個程式梗要有 goto」嘅諗法，確立 goto 係一樣不必要（而且撈絞）嘅嘢，而有電腦科學家指出，放棄 goto 嘅使用令電腦程式變得更加易明同易改。

## 條件決策
條件決策係控制流程陳述式一種，大致上有兩類：

### 條件陳述

條件陳述式（`if... then...`）係絕大多數程式語言都會有嘅一種陳述式，功能係視乎情況決定係咪要做某啲運算同採取某啲行動：一個條件陳述式會掕住一柞碼同一句佢要評估嘅條件，當個程式行到個條件陳述式嗰時，如果個條件係真，個程式就會行條件陳述式掕住嗰柞碼（if 「個條件係真」 then 「行個條件陳述式掕住嘅碼」），否則個程式就唔會行嗰柞碼。典型嘅形態有以下呢啲：
*`IF..GOTO`，如果 .. 為真，goto 某個位，常見於非結構化嘅程式語言。
*`IF..THEN..(ENDIF)`，如果 .. 為真，做 `THEN` 後面嘅 ..，`ENDIF` 用途在於標記掕住碼嘅終結。
*`IF..THEN..ELSE..(ENDIF)`，如果 .. 為真，做 `THEN` 後面嘅 ..，否則做 `ELSE` 後面嘅 ..，`ENDIF` 用途在於標記掕住碼嘅終結。呢個係最常見嘅條件陳述式類型，不過都有啲程式語言唔使用家指明 `ENDIF` 喺邊，而係用空白位等方法嚟斷定邊度係掕住碼嘅終結（例：喺一個 IF 之後，所有掕住碼都要喺最左手邊嗰度有兩個空位），例如機械學習上常用嘅 Python 就係一種啲條件陳述式唔使 `ENDIF` 嘅程式語言。
*`IF..THEN.. ELSE IF .. THEN .. ELSE..(ENDIF)`，一個條件陳述可以**嵌套**（embedded）喺第個條件陳述裏面－如果 .. 為真，做第一個 `THEN` 後面嘅 ..，否則如果 `ELSE IF` 後面嗰個 .. 為真，做第二個 `THEN` 後面嘅 ..，跟住如此類推。

比較少見嘅條件陳述式玩法有：
*有啲程式語言（例子： Fortran），會有所謂嘅「三向」（three-way）條件，測試某個變數數值係正、負、定零嚟決定做乜。
*Perl 有 `if` 跟 `when` 同 `unless`。
*Smalltalk 用 `ifTrue` 同 `ifFalse` 嚟做條件陳述式。

### 開關陳述

開關陳述式（`switch`）會攞個特定變數嘅數值，將個數值同一柞預先講好咗嘅常數嘅數值比較，再選擇同個變數吻合嗰個常數，做嗰個常數後面掕住嗰柞碼。喺多數程式語言入面，段碼仲會掕住一個 `ELSE`，表示「如果嗰個變數唔吻合任何一個常數嘅值，要行嘅碼」。原則上，開關陳述式係不必要嘅，因為一個開關陳述式可以寫做一個有大量 `ELSE IF` 嵌套在內嘅條件陳述式，但一般認為，開關陳述式喺要應對好多個個案嗰陣會清楚簡潔啲。開關陳述式嘅典型形態如下：

```c
switch (age) { /* 攞「age」呢個變數嘅值 */
  case 1:  printf("You're one.");            break; /* 如果 age 數值係 1，做呢段碼。 */
  case 2:  printf("You're two.");            break; /* 如果 age 數值係 2，做呢段碼。 */
  case 3:  printf("You're three.");          break; /* 如此類推... */
  case 4:  printf("You're three or four.");  break;
  default: printf("You're not 1,2,3 or 4!"); /* 如果 age 嘅數值唔係以上任何一個，做呢段碼。 */
}
```


## 迴圈

迴圈（）係另一種不可或缺嘅控制流程。迴圈係一串陳述式，特徵係喺段碼嗰度淨係講咗一次，但會喺個程式行嗰陣行幾次：迴圈會掕住若干句陳述式同重複條件，個程式會按照個迴圈所指定嘅條件，重複噉執行掕住嗰柞陳述式若干次，直至個條件達到為止，而每一次個程式檢查「個條件達到未」就係一個迭代。迴圈嘅使用令到程式編寫方便好多，同一段要用多次嘅碼淨係需打一次。

喺低級程式語言，迴圈可以用對應 goto 嘅指令砌出嚟，但係噉表達好唔清楚。所以廿一世紀常用嘅高級程式語言冚唪唥都會有語法直接表達迴圈呢個概念，有啲仲會可以直接表達多過一種迴圈，例如 While 迴圈、Do-while 迴圈、For 迴圈、Foreach 迴圈同無限迴圈等等；當中例如 C、C++、同 C# 都可以用同一種陳述式（for）直接表達幾乎所有種類嘅迴圈。至於一啲比較理論化嘅概念，例如多重出口迴圈（）同多層離開（）就未必有語法可以表達。

某啲程式語言亦有語法直接表達一啲同迴圈有關嘅特殊動作；以圖中廣義化嘅 For 迴圈為準，呢啲特殊動作可以噉樣形容：
* **跳去下一個迭代**（C 語系：**continue**；Perl 同 Ruby：**next**）即刻直接跳去更新迴圈狀態嘅步驟
* **重做呢個迭代**（Perl 同 Ruby：**redo**）：即係直接跳返去檢查乎唔乎合迴圈條件嘅步驟之後（即係直接返去迴圈內容嘅開頭）
* **重新做個迴圈**：即係直接跳返去設定迴圈狀態嘅步驟

同迴圈有關嘅另外有迴圈變量（）同迴圈不變量（），都係用嚟評估迴圈嘅機制。

## 非區部控制
好多程式語言會容許非區部控制流程嘅功能，即係話俾程式離開某個流程，跟住再喺某個特定嘅點返入去個流程嗰度。常見嘅非區部控制流程功能有以下呢啲：

### 條件式 goto
非區部控制流程最簡單嘅做法係用 goto：例如家陣有一個迴圈，個編程員想個程式喺某啲情況下，跳出個迴圈外面，跟住喺某啲情況下又跳返入去；最直接嘅做法係喺個迴圈入面加好似以下噉嘅條件陳述式：

  **ON** *condition* **GOTO** *label*

然後再喺外面某啲地方加 goto 條件陳述式，令個程式喺某啲情況跳返入個迴圈嗰度。

### 例外處理

例外處理喺電腦編程上係對異常個案嘅處理。廿一世紀嘅程式語言多數都會有一啲特定嘅機制俾用家做例外處理－包括喺行個迴圈，撞到異常情況嗰陣，叫個程式去行第段（迴圈以外嘅）碼再返去個迴圈嗰度。例如 C++ 就有噉嘅功能：


```cpp
#include <iostream> 
using namespace std; 
  
int main() 
{ 
   int x = -1; // 設 x 做 -1

   cout << "Before try \n"; 
   try { 
      cout << "Inside try \n"; 
      if (x < 0) // 如果 x 細過 0
      { 
         throw x; // 「掟」x
         cout << "After throw (Never executed) \n"; 
      } 
   } 
   catch (int x ) { // 掟咗 x 呢一個整數（int；integer）出去嘅話就要行呢段碼
      cout << "Exception Caught \n"; 
   } 
  
   cout << "After catch (Will be executed) \n"; 
   return 0; 
}
```


呢段碼會俾噉嘅輸出：


```cpp
Before try
Inside try
Exception Caught
After catch (Will be executed)
```


以上呢個例子用咗 `throw` 一個整數 `x`，但 `catch` 可以配合任何數量或者類型嘅變數使用，例如 `throw` 兩個整數變數，又或者 `throw` 三個 `float` 類型嘅變數呀噉。如果冇任何一個 `catch` 吻合掟咗出去嗰個或柞變數，個程式通常會去個 `try` 以外搵相應嘅 `catch`（呢點視乎程式語言可能有異），如果搵唔到就會向用家顯示出錯嘅信息。

第啲程式語言當中都有啲類似嘅功能：受到 C++ 影響，有多款程式語言都會用 `catch` 呢個字做同樣嘅功能，例子有 Java 同 C#；另一方面，又有啲語言（例如 Ada）興用 `exception` 呢個關鍵字嚟做例外處理同用第啲關鍵字做 `catch` 嘅功能。好似係以下呢段用 AppleScript 寫成嘅碼噉：


```applescript
try
    set myNumber to myNumber / 0
on error e  number n  from f  to t  partial result pr
    if ( e = "Can't divide by zero" ) then display dialog "You must not do that"
end try
```


#### Finally
某啲程式語言有 `finally` 嘅功能，配合 `try` 嚟使用。無論個程式點樣離開個 `try`，`finally` 掕嗰柞碼都會被執行。喺實際編程上，呢種功能可以要嚟確保個程式喺行完個 `try` 之後實會關某個檔案或者同數據庫斷線（喺某啲情況下可以慳好多資源）。包括 Java、C#、同 Python 等都具有呢種功能，例如以下呢段 C# 碼：


```csharp
FileStream stm = null;
try {
    stm = new FileStream ("logfile.txt", FileMode.Create);
    return ProcessStuff(stm);             // may throw an exception
} finally {
    if (stm != null)
        stm.Close(); // finally 跟嗰柞碼實會行－無論個程式係以乜方法離開個 try
}
```


而因為「想喺做完個迴圈之後關咗個檔案佢」好普遍，有啲程式語言甚至索性有功能做呢樣嘢。例如係 C# 噉：


```csharp
using (FileStream stm = new FileStream ("logfile.txt", FileMode.Create)) {
    return ProcessStuff(stm);             // may throw an exception
}
```


C# 程式語言內置咗，當個程式離開 `using` 嗰陣，個編譯器會確保啲記憶體會釋放 `stm` 呢嚿嘢（`stm` 係檔案等），即係教部電腦暫時將個檔案開咗佢，用完就將佢由記憶體嗰度釋放。Python 嘅 `with` 以及 Ruby 嘅 `File.open` 都會達到類似嘅效果。

### 協程

協程（）係子程式嘅廣義化。當個程式要求行一個子程式嗰時，佢會由個子程式開頭嗰度行，行到尾嗰時就離開個子程式；子程式只會行一次，而喺每次行個子程式之間嗰段時間，部電腦唔會儲住「個子程式喺乜狀態」嘅資訊；相比之下，協程（協程 A）可以要求另一個協程（協程 B），但離開協程 A 去行協程 B 之後，個程式會記住佢喺邊個點離開協程 A 同埋協程 A 當時嘅狀態，行完協程 B 之後可以返去原先喺協程 A 個位繼續行。一段用協程嘅碼望落大致會係噉嘅：

  *var* q := new queue
   
  **coroutine** produce
    **loop**
      **while** q is not full
        create some new items
        add the items to q
        **yield** to consume
  
  **coroutine** consume
    **loop**
      **while** q is not empty
        remove some items from q
        use the items
        **yield** to produce

### 第啲相關功能
*C# 5.0 增加咗 `async` 呢個關鍵字嚟容許異步輸入輸出嘅功能。

## 遊戲編程

控制流程技術喺遊戲編程上不可或缺：電子遊戲程式嘅根基係遊戲迴圈－隻遊戲嘅程式要係噉重複做同一樣嘅工作，攞玩家俾嘅輸入以及隻遊戲喺上一刻嘅狀態，再按照隻遊戲嘅法則計算出遊戲世界下一刻嘅狀態應該係點。即係話隻電子遊戲大致上可以想像成噉嘅碼：

  **while** game is running
    process inputs
    update game world
    generate outputs
  **loop**

以上呢段碼就噉睇落好簡單，但查實可以好複雜：輸入可以包括咗鍵盤、踎士同遊戲機手掣等等，而且控制方案嘅設計可以高深得好緊要（例：「要用邊啲掣做輸入先可以令玩家覺得舒服就手？」）；更新個遊戲世界即係要攞玩家嘅輸入加埋個世界前一刻嘅狀態，用一大堆（表示個遊戲世界運作法則嘅）碼計出下一刻嘅狀態應該係點，例如如果玩家撳咗「向前」，玩家所控制嘅角色嘅位置就要向住佢面向嘅方向改變，而改變幅度由個角色嘅移動速度決定；跟手個程式仲要有計將個新狀態顯示俾喺個熒光幕上面，等個用家有得睇。喺現實應用裏面，上述嘅過程閒閒地可以涉及幾萬行碼。

用以下呢段食鬼嘅虛擬碼為例：

  **while** player.lives > 0 *當玩家有多過 0 條命嗰陣一路做...*
     // Process Inputs
     JoystickData j = grab raw data from joystick *由手掣嗰度探測玩家撳咗乜掣*
     
     // Update Game World
     update player.position based on j *基於玩家撳嘅掣，更新玩家角色嘅位置*
     foreach Ghost g in world *for 每一隻鬼*
        if player collides with g *如果玩家撞到嗰隻鬼*
           kill either player or g *玩家就死*
        else
           update AI for g based on player.position *基於隻鬼嘅人工智能，更新佢嘅位置*
        end
     loop
     
     // Pac-Man eats any pellets
     ...
     
     // Generate Outputs
     draw graphics *喺熒光幕上面畫相應嘅影像*
     update audio *... 同埋整聲效*
  loop

以上嘅碼用咗 while 迴圈（令隻遊戲只要 GAME OVER 條件未達到就一路行）、foreach（處理嗮遊戲世界入面每一件物件）、同條件運算式（如果某啲條件達到，某件物件就要死亡）等嘅多種控制流程機制－控制流程令電子遊戲成為可能。

## 睇埋
*程式語言
*電腦程式編寫
*麵條式程式碼

## 引咗

## 註解

## 文獻

* Abadi, M., Budiu, M., Erlingsson, Ú., & Ligatti, J. (2009). Control-flow integrity principles, implementations, and applications. *ACM Transactions on Information and System Security (TISSEC)*, 13(1), 4.
* Hoare, C. A. R. "Partition: Algorithm 63," "Quicksort: Algorithm 64," and "Find: Algorithm 65." *Comm. ACM 4*, 321-322, 1961.

## 拎

*Go To 陳述式被視為有害嘅
*"用 Go To 陳述式做結構化編程" (PDF).
*"IBM 704 使用手冊" (PDF).
*開心寫 code