控制流程

電腦指令嘅執行順序

控制流程粵拼hung3 zai3 lau4 cing4英文control flow / flow of control)喺電腦科學上係指一個程式當中嘅個體陳述式子程式等以乜嘢次序執行:喺最簡單嘅情況之下,部電腦會將一個程式逐句逐句執行;但現實嘅電腦程式好少可會咁簡單,所以常用嘅程式語言冚唪唥都會包含控制流程陳述式(control flow statement)嚟俾用家操控(例如)「喺乜情況下會執行呢柞陳述式,乜情況下唔會」,或者「幾時要重複執行呢柞陳述式」呀噉[1][2][3]

If-then 條件運算式係最常用嘅控制流程之一。上圖左上角嗰四句碼教部電腦「如果 A 係真,就行 B 呢柞陳述式;否則就行 C 呢柞陳述式」。

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

 if (this unit's health <= 0) 
   // 如果個單位嘅生命值細過或者等如 0,
   This unit is dead; // 就要當佢死咗
   Remove it from the game world; // 將佢由遊戲世界嗰度清除

呢段碼會令到個程式「如果達到呢個條件,先行呢幾行碼」-令個程式唔係簡單噉逐句逐句行嗮佢,所以係控制流程技術嘅一種[4]

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

大分類

編輯
 
一啲常見嘅控制流程嘅相應圖解:
(a) If-then-else
(b) while loop
(c) 有兩個出口嘅 natural loop
(d) 有兩個入口一個出口嘅 loop

控制流程大致上可以分做幾類,以下呢五類喺多數嘅主流程式語言當中都可以搵到[1][2]

  • 由一個陳述式無條件噉跳去第個嗰度(分支);
  • 淨係喺某啲條件達到嗰時先執行某柞陳述式(條件運算式開關運算式);
  • 一路行某柞陳述式,直到某個條件達到為止(迴圈);
  • 執行一柞喺好遠嘅陳述式,跟住再返去而家呢個位(子程式);
  • 未執行完個程式最後嗰句陳述式,照樣終止程式(無條件終止)。

基本概念

編輯

標記

編輯

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

程式行號(line number)係喺好多程式語言(包括 PythonMATLAB 等)當中都有嘅一種標記,指段源碼每行前面都有一個自然數,呢啲數字一般都會負責指明嗰行係段源碼入面嘅第幾行[6]。不過都有啲程式語言唔使啲行號連續,淨係需要啲行號係愈落個數愈大就得,好似係以下呢段 BASIC 碼噉,第一行碼嘅行號係「10」,而第二行碼嘅行號係「20」[7]

10 LET X = 3
20 PRINT X

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

Success: printf("The operation was successful.\n");
内文:Goto

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

   goto label

組合語言低級程式語言裏面,對應 goto 嘅 jump 或者 branch 基本上係控制流程嘅唯一方法。但喺高級程式語言裏面,goto 到咗今下經已唔多常用,因為 goto 俾人覺得佢好唔方便-如果喺一隻用數字做標記嘅程式語言入面用 goto,而打後個編程者加咗幾段碼嘅話,就可能會搞到啲標記走嗮位,要人手噉逐個逐個 goto 執返好佢;所以喺實際嘅高級語言編程裏面,啲人好少可用 goto(睇埋下面結構化程式定理[10]

子程式

編輯
内文:子程式

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

  子程式 a
    講好個子程式包含乜陳述式

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

結構化編程

編輯

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

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

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

結構化程式定理

編輯

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

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

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

然後再跟以下規則改寫段碼:

  1. 將所有「 」改做「if (l == i) then do  , l ← l + 1」;
  2. 將所有「   goto  」改做「if (l = i) then l ← j - 1」(「a ← b」喺編程上係指「將 a 嘅數值設成 b」);
  3. 將所有「 if (cond) then goto  」改做「if ((l == i) AND (cond == true)) then l ← j else l ← l + 1」;

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

條件決策

編輯

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

條件陳述式

編輯

條件陳述式(conditional statement;if... then...)係絕大多數程式語言都會有嘅一種陳述式,功能係視乎情況決定係咪要做某啲運算同採取某啲行動:一個條件陳述式會掕住一柞碼同一句佢要評估嘅條件,當個程式行到個條件陳述式嗰時,如果個條件係真,個程式就會行條件陳述式掕住嗰柞碼(if 「個條件係真」 then 「行個條件陳述式掕住嘅碼」),否則個程式就唔會行嗰柞碼。典型嘅形態有以下呢啲[16]

  • 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 後面嘅 ..,跟住如此類推。
Pascal: Ada: C: Shell script: Python: Lisp:
if a > 0 then
  writeln("yes")
else
  writeln("no");
if a > 0 then
      Put_Line("yes");
else
      Put_Line("no");
end if;
if (a > 0) { 
      printf("yes");
} else {
      printf("no");
}
if [ $a -gt 0 ]; then
      echo "yes"
else
      echo "no"
fi
if a > 0: 
      print("yes")
else:
      print("no")
(princ
  (if (plusp a)
      "yes"
      "no"))

比較少見嘅條件陳述式玩法有:

  • 有啲程式語言(例子: Fortran),會有所謂嘅「三向」(three-way)條件,測試某個變數數值係正、負、定零嚟決定做乜。
  • Perlifwhenunless
  • SmalltalkifTrueifFalse 嚟做條件陳述式。

開關陳述式

編輯

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

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 嘅數值唔係以上任何一個,做呢段碼。 */
}
Pascal: Ada: C: Bourne shell: Lisp:
case someChar of
  'a': actionOnA;
  'x': actionOnX;
  'y','z':actionOnYandZ;
  else actionOnNoMatch;
end;
case someChar is
  when 'a' => actionOnA;
  when 'x' => actionOnX;
  when 'y' | 'z' => actionOnYandZ;
  when others => actionOnNoMatch;
end;
switch (someChar) {
  case 'a': actionOnA; break;
  case 'x': actionOnX; break;
  case 'y':
  case 'z': actionOnYandZ; break;
  default: actionOnNoMatch;
}
case $someChar in 
   a)    actionOnA ;;
   x)    actionOnX ;;
   [yz]) actionOnYandZ ;;
   *)    actionOnNoMatch  ;;
esac
(case someChar
  ((#\a)     actionOnA)
  ((#\x)     actionOnX)
  ((#\y #\z) actionOnYandZ)
  (else      actionOnNoMatch))

迴圈

編輯
 
For 迴圈嘅控制流程圖
内文:迴圈

迴圈(loop)係另一種不可或缺嘅控制流程陳述式。一個迴圈係一串陳述式,特徵係喺段碼嗰度淨係講咗一次,但會喺個程式行嗰陣行幾次:一個迴圈會掕住若干句陳述式同重複條件,個程式會按照個迴圈所指定嘅條件,重複噉執行掕住嗰柞陳述式若干次,直至個條件達到為止,而每一次個程式檢查「個條件達到未」就係一個迭代(iteration)。迴圈嘅使用令到程式編寫方便好多,同一段要用多次嘅碼淨係需打一次[18]。廿一世紀常用嘅程式語言冚唪唥都會包含迴圈,而一啲高級程式語言仲會有多過一種迴圈,例如 C 程式語言C++、同 C# 等都支援好幾種迴圈嘅使用,當中 C 仲可以用一種陳述式包嗮所有種類嘅迴圈[19]

For 迴圈

編輯
内文:For 迴圈

For 迴圈(for loop)係幾乎所有程式語言都有嘅迴圈。一個 for 迴圈會有個關鍵字,後面有一個條件,而跟住嗰幾行碼就係掕住碼,只要個條件一日係真,個程式會一路係噉行柞掕住碼,例如係以下呢段用 Java 寫嘅碼噉[20]

for (int i = 0; i < 100; i++)  // 設一個變數 i,其數值係 0;只要 i 細過 100,就一路行 {} 以內嘅碼,每行一次將 i 數值加 1(所以段碼會行 100 次)。
{
    System.out.print(i); // output i 嘅數值
    System.out.print(' '); // 後面要有 space
}
System.out.println();
// 呢段碼會出嘅係 0 至 99 嘅一串數字,而且每個數字之間有個 space。

喺一個 for 迴圈當中用嚟數迭代嗰個變數(此後叫「i」)跳嘅一步好多時可以預設當做 1:原則上,i 每次跳可以跳多過 1 而成個 for 迴圈功能不變,例如 (int i = 0; i < 200; i = i + 2) 查實一樣會令段掕住碼行 100 次;不過喺實際應用上,好多人都嫌吓吓要指明「i 每步要跳 1」麻煩,所以唔少程式語言會預設咗 i 每步都係跳 1,唔使用家講明。好似係 MATLAB[21]

    for n = 1:100 % 有個變數 n,其數值係 1;只要 n 喺 1 同 100 之間,就一路行直至 end 為止嘅碼,每行一次將 n 數值加 1(所以段碼會行 100 次)。
        ............
    end

While 迴圈

編輯
内文:While 迴圈

While 迴圈(while loop)係廿一世紀初多數程式語言都有嘅迴圈。一個 while 迴圈會有一個條件,同一柞掕住嘅碼。個程式會評估個條件,如果個條件係真,噉就會行柞掕住碼,行完一次之後再睇吓個條件係咪真,如果係就再行多次柞掕住碼,一路重複直至個條件唔係真止。While 迴圈一個應用例子係電子遊戲編程:一隻電子遊戲嘅一場對局可以想像成個遊戲程式一路做場對局嘅運算,直至某個 GAME OVER 條件(例如玩家角色生命值變咗 0)達到為止。以下呢段 MATLAB 碼用咗 while 迴圈計 10 嘅階乘(factorial)[22]

    n = 10; % 設 n 做 10。
    f = n; % 設 f 等同 n。
    while n > 1 % 只要 n 大過 1,就一路做以下嘅嘢:
        n = n - 1; % 將 n 數值下降 1。
        f = f * n; % 將 f 變成 f 乘 n。
    end
    disp(['n! = ' num2str(f)]) % show "n! = " 同 f 最後個數值;呢段碼計出嘅係 10 嘅 factorial。

Do-while 迴圈

編輯

Do-while 迴圈(do-while loop)係同 while 迴圈好相似嘅迴圈,分別在於幾時評估個條件:一個 do-while 迴圈都係會有一個條件,同一柞掕住嘅碼。個程式會行一次段掕住碼,睇吓個條件係咪真,如果係,噉就再行多次段掕住碼,跟住再睇吓個條件係咪真,如此類推。喺一個程式嘅執行過程當中,一個 do-while 迴圈起碼會行一次,而一個 while 迴圈有機會可以完全一次都唔行,所以 do-while 迴圈有陣時可以做啲 while 迴圈做唔到嘅效果,不過有好多受歡迎嘅程式語言都冇 do-while 迴圈嘅功能(例子有 Python)[23]

以下呢段 Java 碼就用咗 do-while 迴圈[24]

public class DoWhileExample {  
public static void main(String[] args) {  
    int i = 1; // 設 i 嘅數值係 1。  
    do{  
        System.out.println(i); // show i 嘅數值。
    i++;  // 將 i 嘅數值上升 1。
    }while(i <= 10);  // do {} 入面嘅碼 while i 細過或者等如 10。
}  
} // 呢段碼會將 1 到 10 嘅數字 show 喺 output 嗰度。

Foreach 迴圈

編輯

Foreach 迴圈(for each loop)係一種迴圈,會叫個程式攞某柞嘢,(for)柞嘢當中每一件(each)行段掕住碼一次。Foreach 迴圈最常係夾埋數組(array;一個數組包含一列各自獨立嘅數值)一齊用,例子有以下呢段 Java 碼[25]

class ForEachExample1{  
  public static void main(String args[]){  
    int arr[] = {12,13,14,44}; // 整一個 array,名為 arr,個 array 包含 12、13、14、同 44 呢幾個數值。
    for(int i:arr){ // for arr 入面每個元素,做...
      System.out.println(i);  // show i 嘅數值出嚟睇。
    }
  }   
} // 呢段碼會喺 output 嗰度 show 出 12、13、14、同 44 呢幾個數字。

就算個數組入面嘅嘢唔係數字,foreach 迴圈都行得通[25]

import java.util.*;
class ForEachExample2{
  public static void main(String args[]){
    ArrayList<String> list=new ArrayList<String>(); // 整一個 array,個名叫「list」,用嚟裝 string(文字)。
    list.add("vimal"); // 喺 list 加入「vimal」呢個元素。
    list.add("sonoo"); // 如此類推...
    list.add("ratan");

    for(String s:list){ // for list 嘅每個元素,做...
      System.out.println(s); // show 個元素出嚟睇。
    }
  } // 呢段碼會喺 output 嗰度 show 出「vimal sonoo ratan」。
}

無限迴圈

編輯
内文:無限迴圈

無限迴圈(infinite loop)係指一個永遠都行唔完嘅迴圈,原因可能係因為個迴圈根本冇終止條件、有一個冇可能達得到嘅終止條件、又或者有一啲會搞到個迴圈重新啟動嘅陳述式-通常係個編程員無意中犯錯先會噉。喺原始啲嘅廿世紀電腦當中,無限迴圈會搞到部電腦輕機,不過先進啲嘅電腦會識喺出現無限迴圈嗰時話俾個使用者知,等個使用者決定好唔好繼續行落去,又或者俾個用家喺無限迴圈出現嗰時人手終止個程式[26]

無限迴圈例子有以下呢啲[27]

一段 Java 碼:

while (true) // 當「true」係真嘅時候一路做以下嘅嘢;因為 true by definition 就係真,所以呢個 loop 永遠唔會完。
    System.out.println("Infinite Loop");

一段 Visual Basic 碼:

dim x as integer ' 設 x 呢個變數,佢係一個整數
do while x < 5 ' do 以下嘅嘢 while x 細過 5
  x = 1 ' 設 x 做 1
  x = x + 1 ' 設 x 做 x + 1
loop
' 呢段碼個迴圈開頭會將 x 嘅數值設返做 1,所以 x 嘅數值永遠都唔會等如或者大過 5(有一個冇可能達得到嘅終止條件)。

提早離開迴圈

編輯

有陣時,個編程員會想個程式喺某個條件達到嗰時停止迴圈。例如家陣有段碼,用個 while 迴圈係噉耖一柞數據,由頭耖到落尾,理想嘅話,個程式喺搵到想要嗰個數據嗰時,會離開個迴圈,而唔係繼續耖柞數據。因為呢個緣故,廿一世紀初嘅程式語言多數都會支援 break(離開迴圈)嘅功能;同時,Visual basic 有 exit,而 Perl 有 last,兩者都係做同樣嘅功能-即刻終止個迴圈,令個程式直接行個迴圈嘅下一行陳述式。呢種情況有時會俾人嗌做「提早離開迴圈」(early-exit loop)[28]

提早離開迴圈典型嘅做法係有個迴圈,喺個迴圈入面有一個條件陳述式,如果一個條件成立,個程式就要行 break,否則個程式就繼續行個迴圈。好似係以下呢段用 C 寫嘅碼噉[28]

#include <stdio.h>
int main()
{
     int num = 0;  /* 設 num 係 0 */
     while (num <= 100) /* while num 細過等如 100,做... */
     {
        printf("value of variable num is: %d\n", num); /* show 出... */
        if (num == 2) /* if num 等如 2 */
        {
            break; /* 離開個迴圈,行迴圈之後嗰行碼。 */
        }
        num++; /* 將 num 數值加 1 */
     }
     printf("Out of while-loop");
     return 0;
}

呢段碼嘅輸出會係噉嘅[28]

value of variable num is: 0
value of variable num is: 1
value of variable num is: 2
Out of while-loop

離開決策

編輯

某啲程式語言仲支援「視乎個迴圈有冇 break,決定行唔行某一段碼」,例如以下呢段 Python 碼[29]

for val in "string":
    if val == "n":
        print("found")
        break
else:
    print("The loop didn't break")

呢段碼會俾:

found

而如果將段碼改做:

for val in "string":
    if val == "z":
        print("found")
        break
else:
    print("The loop didn't break")

就會出:

The loop didn't break

即係話 else: 跟住嗰段碼只會喺個迴圈冇 break 嗰陣被執行。

多層離開

編輯

喺要應付嵌套住嘅迴圈(nested loop;喺另一個迴圈內嘅迴圈)嗰陣,break 嘅使用就比較撈絞[30][31][32]

  • 喺好多程式語言入面(例如 Python),break 只會終止一層迴圈,即係話如果有一個嵌套住嘅迴圈嗰度做 break,外層嗰個迴圈會如常噉繼續行;
  • 另一方面,有啲程式語言會俾用家用 break 一吓離開嗮行緊嘅迴圈,而喺理論性嘅編程研究上,呢個動作就係所謂嘅多層離開(multi-level break);
  • 此外,常見嘅做法仲有將個迴圈擺喺一個子程式當中,再喺個子程式裏面用 return(終止個子程式)一吓離開成個(包含咗一大柞迴圈嘅)子程式;
  • 再唔係有人會索性用 goto 離開一柞行緊嘅迴圈。

有唔少編程專家都懷疑「容許用家一嘢離開嗮所有行緊嘅迴圈」係咪一樣好嘢,例如喺廿一世紀初嗰陣,Python 就試過有人提議加多層離開功能,但就俾人以「呢個功能會造成多餘嘅複雜性,而佢嘅用途太少,根本唔值得加」為由否決[33]

多層離開嘅諗頭喺理論電腦科學(theoretical computer science)當中引起唔少人思考:喺 1973 年,有電腦科學家執咗吓結構化程式定理,跟手仲證明,只要一隻程式語言有多層離開嘅功能,就可以避免加多啲變數-是但搵個整數 n,都會有個程式具有一個離開 n 個迴圈嘅多層離開,而呢個程式可以重寫做一個具有離開 m 個迴圈(m < n)、而且變數數量一樣或者更少嘅程式。既然多層離開嘅使用容許一個程式有少啲變數,即係呢種功能喺某啲情況下可以減低程式嘅複雜性[34][35]

其他相關功能

編輯
  • 跳去下一個迭代:喺某啲編程應用當中,編程員有陣時會想個程式跳過個迴圈嘅剩餘部份,直接進入下一個迭代;有啲程式語言有陳述式做噉嘅嘢,例如多數語言有嘅 continue、Perl 同 Ruby 當中有嘅 next、同 skip。呢啲陳述式一定要搭配迴圈使用,會結束目前嗰個迭代,直接跳去行下一個迭代,而如果個迭代係最後一個,個 continue 會直接終止個迴圈。例如係以下呢段 C 碼[36]
# include <stdio.h>
int main() /* 呢段碼會俾個用家輸入若干個數字,再將啲數字加埋一齊。 */
{
    int i;
    double number, sum = 0.0;
    for(i = 1; i <= 10; ++i)
    {
        printf("Enter a n%d: ",i);
        scanf("%lf",&number);
        if (number < 0.0)
        {
            continue; /* 如果輸入嗰個數字細過 0,跳去下一個迭代。 */
        }
        sum += number;
    }
    printf("Sum = %.2lf",sum);
    return 0;
}

呢段碼嘅輸出望落會係噉嘅:

Enter a n1: 1.1
Enter a n2: 2.2
Enter a n3: 5.5
Enter a n4: 4.4
Enter a n5: -3.4
Enter a n6: -45.5
Enter a n7: 34.5
Enter a n8: -4.2
Enter a n9: -1000
Enter a n10: 12
Sum = 59.70
  • 重做呢個迭代:Perl 同 Ruby 有 redo 嘅陳述式,會令個程式重新行過個迭代[37]
  • 重新做個迴圈:Ruby 有 retry 嘅陳述式,會令個程式將成個迴圈重新行過[37]

變量同不變量

編輯

迴圈變量(loop variant)同迴圈不變量(loop invariant)係用嚟評估迴圈嘅機制[38]

  • 迴圈變量指一個數值會隨住迭代而減少嘅變數,設嚟監察個迴圈運作係咪如常。例如係喺以下呢段 Whiley 碼當中,|items| - i 會係一個啱用嘅迴圈變量[39]
function contains([int] items, int item) => bool:
    int i = 0 // 設 i
    //
    while i < |items|:
        if items[i] == item:
            return true
        i = i + 1 // 喺每一個迭代當中,i 數值都會上升 1
    // 「|items| - i」嘅數值一定會隨住迭代而下降。
    // 編程員可以喺度加段陳述式,叫個程式喺 output 嗰度每迭代顯示 (|items| - i) 嘅值,跟住佢就可以用眼 monitor 住個迴圈運作係咪如常。
    return false
  • 迴圈不變量係一個數值理應唔會隨迭代改變嘅變數,都係整嚟監察個迴圈運作係咪如常嘅。例如有以下呢段 C 碼[40]
int max(int n,const int a[]) { // 呢個子程式會攞一個 array,再搵出個 array 裏面最大嗰個數
    int m = a[0]; // a[0] 係指 a 呢個 array 嘅第 1 個數
    int i = 1;
    while (i != n) { // 如果 i 數值唔等如 n,一路做...
        if (m < a[i])
            m = a[i]; // 如果 m 數值細過 a 嘅第 (i + 1) 個數值,將 m 設做 a[i]
        ++i; // i 數值升 1
    }
    return m;
} // 一個可能嘅迴圈不變量係 a[] 嘅第一個數;個編程員可以加句陳述式,叫個程式每次迭代都 display a[0] 出嚟-如果個程式運作正常,a[0] 數值理應會維持不變。

非區部控制流程

編輯

有好多隻程式語言會容許非區部控制流程(non-local control flow)嘅功能,即係話俾一個程式離開某個流程,跟住再喺某個特定嘅點返入去個流程嗰度。常見嘅非區部控制流程功能有以下呢啲[41]

條件式 goto

編輯

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

 ON condition GOTO label

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

例外處理

編輯
内文:例外處理

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

#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; 
}

呢段碼會俾噉嘅輸出[42]

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

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

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

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 嚟使用。無論個程式點樣離開個 tryfinally 掕嗰柞碼都會被執行。喺實際編程上,呢種功能可以要嚟確保個程式喺行完個 try 之後實會關某個檔案或者同數據庫斷線(喺某啲情況下可以慳好多資源)。包括 Java、C#、同 Python 等都具有呢種功能,例如以下呢段 C# 碼[45]

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# 噉:

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 都會達到類似嘅效果[46]

協程

編輯
内文:協程

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

 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

第啲相關功能

編輯

遊戲編程應用

編輯
睇埋:遊戲編程

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

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

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

1982 年嘅食鬼遊戲

用以下呢段食鬼虛擬碼為例[49]

 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(處理嗮遊戲世界入面每一件物件)、同條件運算式(如果某啲條件達到,某件物件就要死亡)等嘅多種控制流程機制-控制流程令電子遊戲成為可能[49]

睇埋

編輯
  1. 1.0 1.1 Shivers, O. (1991). Control-flow analysis of higher-order languages (Doctoral dissertation, PhD thesis, Carnegie Mellon University).
  2. 2.0 2.1 2.2 Shivers, O. (1988, June). Control flow analysis in scheme. In ACM SIGPLAN Notices (Vol. 23, No. 7, pp. 164-174). ACM.
  3. R: Control Flow 互聯網檔案館歸檔,歸檔日期2019年9月2號,..
  4. 4.0 4.1 Learning Computer Science Through Games: Conditionals and If-Else Statements 互聯網檔案館歸檔,歸檔日期2019年9月1號,.. Cloudy Heaven Games.
  5. Frances E. Allen (July 1970). "Control flow analysis". SIGPLAN Notices. 5 (7): 1–19.
  6. 6.0 6.1 Label 互聯網檔案館歸檔,歸檔日期2019年7月21號,.. Computer Hope.
  7. Why did BASIC use line numbers?.
  8. C Standard section 6.8.6.1 The goto statement 互聯網檔案館歸檔,歸檔日期2007年12月24號,..
  9. David Anthony Watt; William Findlay (2004). Programming language design concepts. John Wiley & Sons. p. 228.
  10. McShaffry, M. (2014). Game coding complete. Nelson Education. p. xlii.
  11. Wheeler, D. J. (1952). "The use of sub-routines in programmes". Proceedings of the 1952 ACM national meeting (Pittsburgh) on - ACM '52.
  12. 12.0 12.1 Dijkstra, E. W. (1970). Notes on structured programming.
  13. 13.0 13.1 Böhm, Jacopini. "Flow diagrams, turing machines and languages with only two formation rules" Comm. ACM, 9(5):366-371, May 1966.
  14. How to prove the structured program theorem? 互聯網檔案館歸檔,歸檔日期2019年9月3號,.. Computer science questions.
  15. Leavenworth, B. M. (1972, August). Programming with (out) the GOTO. In Proceedings of the ACM annual conference - Volume 2 (pp. 782-786). ACM.
  16. Gabbay, D., Giordano, L., Martelli, A., Olivetti, N., & Sapino, M. L. (2000). Conditional reasoning in logic programming. The Journal of Logic Programming, 44(1-3), 37-74.
  17. switch vs if else 互聯網檔案館歸檔,歸檔日期2019年9月4號,.. GeeksforGeeks.
  18. McShaffry, M. (2014). Game coding complete. Nelson Education. p. 34.
  19. Definition of a Loop. ThoughtCo.
  20. Loops in Java.
  21. Loop Control Statements. MathWorks.
  22. while. MathWorks.
  23. Python Do While Loop.
  24. Java do-while Loop.
  25. 25.0 25.1 Java For-each Loop | Enhanced For Loop.
  26. Hoare, C. A. R. (1969). An axiomatic basis for computer programming. Communications of the ACM, 12(10), 576-580.
  27. Meerbaum-Salant, O., Armoni, M., & Ben-Ari, M. (2011, June). Habits of programming in scratch. In Proceedings of the 16th annual joint conference on Innovation and technology in computer science education (pp. 168-172). ACM.
  28. 28.0 28.1 28.2 C – break statement in C programming 互聯網檔案館歸檔,歸檔日期2019年9月6號,..
  29. Python break and continue 互聯網檔案館歸檔,歸檔日期2019年9月6號,..
  30. comp.lang.c FAQ list. "Question 20.20b".
  31. Breaking out of two loops 互聯網檔案館歸檔,歸檔日期2019年9月6號,..
  32. David Anthony Watt; William Findlay (2004). Programming language design concepts. John Wiley & Sons. pp. 215–221.
  33. [Python-3000 Announcing PEP 3136] 互聯網檔案館歸檔,歸檔日期2019年9月6號,., Guido van Rossum.
  34. Kozen, Dexter (2008). The Böhm–Jacopini Theorem Is False, Propositionally (PDF). Lecture Notes in Computer Science. 5133. pp. 177–192.
  35. Kosaraju, S. Rao. "Analysis of structured programs," Proc. Fifth Annual ACM Syrup. Theory of Computing, (May 1973), 240-252; also in J. Computer and System Sciences, 9, 3 (December 1974). cited by Donald Knuth (1974). "Structured Programming with go to Statements". Computing Surveys. 6 (4): 261–301.
  36. C break and continue 互聯網檔案館歸檔,歸檔日期2019年9月6號,..
  37. 37.0 37.1 Flanagan, D., & Matsumoto, Y. (2008). The Ruby Programming Language: Everything You Need to Know. O'Reilly Media, Inc.
  38. Meyer, Bertrand (1991). Eiffel: The Language. Prentice Hall. pp. 129–131.
  39. Winskel, Glynn (1993). The Formal Semantics of Programming Languages: An Introduction. Massachusetts Institute of Technology. pp. 32–33, 174–176.
  40. David Gries. "A note on a standard strategy for developing loop invariants and loops." Science of Computer Programming, vol 2, pp. 207–214. 1984.
  41. 41.0 41.1 Krebbers, R., & Wiedijk, F. (2013, March). Separation logic for non-local control flow and block scope variables. In International Conference on Foundations of Software Science and Computational Structures (pp. 257-272). Springer, Berlin, Heidelberg.
  42. 42.0 42.1 42.2 Exception Handling in C++ 互聯網檔案館歸檔,歸檔日期2017年11月21號,..
  43. Liskov, B. H., & Snyder, A. (1979). Exception handling in CLU. IEEE transactions on software engineering, (6), 546-558.
  44. Goodman, D. (1994). Danny Goodman's AppleScript Handbook. Alfred A. Knopf, Inc..
  45. try-finally (C# Reference) 互聯網檔案館歸檔,歸檔日期2019年7月13號,..
  46. Understanding Python's "with" statement 互聯網檔案館歸檔,歸檔日期2019年3月5號,..
  47. Knuth, Donald Ervin (1997). Fundamental Algorithms. The Art of Computer Programming. 1 (3rd ed.). Addison-Wesley. Section 1.4.2: Coroutines, pp. 193–200.
  48. Asynchronous programming with async and await.
  49. 49.0 49.1 49.2 49.3 Game Programming Algorithms and Techniques: Overview, p. 2. informIT.
  50. Valente, L., Conci, A., & Feijó, B. (2005). Real time game loop models for single-player computer games. In Proceedings of the IV Brazilian Symposium on Computer Games and Digital Entertainment (Vol. 89, p. 99).

文獻

編輯
  • 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.