複製鏈接
請複製以下鏈接發送給好友

流程控制

鎖定
控制流程(也稱為流程控制)是計算機運算領域的用語,意指在程序運行時,個別的指令(或是陳述、子程序)運行或求值的順序。不論是在聲明式編程語言或是函數編程語言中,都有類似的概念。
中文名
流程控制
別    名
控制流程
類    別
計算機運算領域的用語
釋    義
個別的指令運行或求值的順序

流程控制基本概念

在聲明式的編程語言中,流程控制指令是指會改變程序運行順序的指令,可能是運行不同位置的指令,或是在二段(或多段)程序中選擇一個運行。
不同的編程語言所提供的流程控制指令也會隨之不同,但一般可以分為以下四種:
  • 繼續運行位在不同位置的一段指令(無條件分支指令)。
  • 若特定條件成立時,運行一段指令,例如C語言的switch指令,是一種有條件分支指令。
  • 運行一段指令若干次,直到特定條件成立為止,例如C語言的for指令,仍然可視為一種有條件分支指令。
  • 運行位於不同位置的一段指令,但完成後會繼續運行原來要運行的指令,包括子程序協程(coroutine)及延續性(continuation)。
  • 停止程序,不運行任何指令(無條件的終止)。
中斷以及Unix系統中的信號等較低級的機制也可以造成類似子程序的效果,不過通常這類機制會用來回應外部的事件或是輸入。程序自修改因為其對代碼的影響,也會影響控制流程,但多半不會有明顯的流程控制指令。
機器語言彙編語言中,流程控制是藉由修改程序計數器數值來達到。一些中央處理器只支持條件分支(branch)或是無條件分支(有時會稱為jump)。

流程控制標記

標記是一個標示在源代碼固定位置中的名稱或數字,其他位置的流程控制指令可以參考標記的位置,運行標記位置所對應的程序。標記本身不影響程序的進行,除了標示位置外,對程序運行沒有其他的作用。
有一些編程語言(像Fortran及BASIC等)利用行號作為標記。行號是標示在每一行程序最前面的自然數,不一定要是連續的數字,在不受流程控制指令影響的情形下,程序會從最小的行號依序運行,而流程控制指令需指定對應的行號。以下是一個BASIC的例子:
LETX=3
PRINT"*"
LETX=X-1
IFX>0THENGOTO20
END
在像是C及Ada等編程語言中,標記是一個標識符,一般出現在一行的最前面,後面會加一個冒號作為識別,以下是C語言的例子:
Success: printf ("The operation was successful.\n");
Algol 60語言同時支持整數(類似行號)及標識符的標記(二者後面都要加上冒號),不過其他Algol語言幾乎都不支持整數的標記。

流程控制Goto

goto 指令(來自英文go和to的組合)是無條件流程控制指令中最基本的型式。一般在程序中會用以下的方式出現(指令大小寫可能會依編程語言而不同)
gotolabel
goto 指令的效果是調整程序的控制流程,後續就運行標記位置的程序。
goto 指令是許多的計算機科學家視為有害(considered harmful)的指令,例如Edsger Wybe Dijkstra提出了goto有害論。 [1] 

流程控制子程序

子程序(subroutine)可以用許多不同的術語來表示,例如程序、函數(尤其是有傳回值時)或是方法(特別是子程序屬於一個類的一部份)等。
子程序是是完成一項特定工作的代碼串行,其他程序可以將流程移轉到子程序中,運行特定工作後再回到原來的程序,若程序中有許多部份都需要運行一特定工作,利用子程序的方式可以利用一段程序達到上述的功能,可以減少代碼的長度。
如今子程序也常用來使得程序更加的結構化,例如可以將一些特殊的算法或特殊的數據訪問方式放在子程序中,和其他代碼隔離。子程序也是程序模塊的一種,若許多程序員共同開發一個程序,子程序也有助於其工作的分區及分工。

流程控制控制流程

1966 年5月Corrado Böhm及Giuseppe Jacopini在《Communications of the ACM》發表論文,説明任何一個有goto指令的程序,可以改為完全不使用goto指令的程序,goto指令可以用選擇指令(IF THEN ELSE)及循環(WHILE 特定條件 DO 特定程序)取代,可能會再多一些重複的代碼及額外的布林變量。後來的研究者已證明選擇指令也可以用循環取代,不過需要更多的布林變量。Böhm及 Jacopini的論文説明程序可以完全不使用goto,但是在實務上大家不一定會想要這麼進行。
其他的研究説明若控制結構只有一個進入點(entry)及一個退出點(exit),這樣的程序會比其他型式的程序容易理解。因此這樣的程序可以像一個指令一樣放在程序的任何部份,不必擔心會破壞其結構,換句話説,這種程序是“可組成的”(composable)。 [2] 

流程控制控制結構

若一編程語言支持控制結構,控制結構開始時多半都會有特定的關鍵字,以標明是使用哪一種控制結構。但只有部份編程語言在控制結構退出時會有特定的關鍵字表示退出,因此可以依控制結構退出時是否有特定關鍵字來將編程語言分為二類。
  • 沒有特定關鍵字的語言:Algol 60、C、C++Haskell、Java、PascalPerlPHPPL/IPythonWindows PowerShell。這類語言需要有關鍵字可以將group程序指令together:
    • Algol 60及 Pascal:begin ... end
    • C, C++, Java, Perl, PHP, and PowerShell:利用大括號{ ... }
    • PL/1:DO ... END
    • Python:利用縮進indentation)的層次,詳細內容請參考Off-side規則
    • Haskell:可以利用縮進或大括號,兩者可以混用
  • 有特定關鍵字的語言:Ada、Algol 68、 Modula-2(Modula-2)、Fortran 77、Visual Basic,使用的特定關鍵字依編程語言而不同:
    • Ada: 其關鍵字為 end + space + 啓始控制結構的關鍵字,如if ... end if, loop ... end loop
    • Algol 68, Mythryl:將啓始關鍵字反寫,如if ... fi, case ... esac
    • Fortran 77: 其關鍵字為 end + initial keyword,如IF ... ENDIF, DO ... ENDDO
    • Modula-2: 不論何種控制結構,其關鍵字均為END
    • Visual Basic: 每種控制結構均有各自的結尾關鍵字,如If ... End If; For ... Next; Do ... Loop

流程控制條件判斷

條件判斷是依指定變量或表達式的結果,決定後續運行的程序,最常用的是if-else指令,可以根據指定條件是否成立,決定後續的程序。也可以組合多個if-else指令,進行較複雜的條件判斷。 許多編程語言也提供多選一的條件判斷,例如C語言的switch-case指令。 [3] 

流程控制循環

循環是指一段在程序中只出現一次,但可能會連續運行多次的代碼。常見的循環可以分為二種,指定運行次數的循環(如C語言的for循環)以及指定繼續運行條件(或停止條件)的循環(如C語言的while循環)。
在一些函數編程語言(例如HaskellScheme)中會使用遞歸不動點組合子來達到循環的效果,其中尾部遞歸是一種特別的遞歸,很容易轉換為迭代。

流程控制非區部控制流程

有些編程語言會提供非區部的控制流程(non-local control flow),會允許流程跳出目前的代碼,進入一段事先指定的代碼。常用的結構化非區部控制流程可分為條件處理(condition)、異常處理及延續性(Continuation)三種。

流程控制條件處理

PL/I編程語言中有22種標準的條件(如 ZERODIVIDE SUBSCRIPTRANGE ENDFILE),可以在程序中設置,當特定條件成立時需進行的指令,程序設計者也可以定義自己的條件,並在程序中使用。
條件成立時,只能設置一個需進行的指令(類似未結構化的if指令),大部份的應用中,都會指定運行goto指令,跳到其他代碼運行對應的流程。
不過因為有些條件處理的實現會增加許多代碼及運行時間(特別SUBSCRIPTRANGE),所以許多程序設計者會盡量不使用條件處理。
條件處理的語法如下:
ON 條件 GOTO label

流程控制異常處理

有些編程語言可提供不需要使用GOTO的結構化異常處理程序:
try{xxx1//Somewhereinherexxx2//use:'''throw'''someValue;xxx3}catch(someClass&someId){//catchvalueofsomeClassactionForSomeClass}catch(someType&anotherId){//catchvalueofsomeTypeactionForSomeType}catch(...){//catchanythingnotalreadycaughtactionForAnythingElse}
在try{...}的區塊中,若 有異常情形時,程序就會離開try的區塊,由後續的一個或多個catch子句判斷需運行何種異常處理。在D、Java、C#及Python編程語言 中,try{...}區塊中還可以加入一個finally子句,不管程序流程是否離開try{...}區塊,finally子句中的程序都一定會運行,常 用在當程序退出處理時,需要放棄一些外部資源(文件或數據庫鏈接)的情形下:
FileStreamstm=null;//C#exampletry{stm=newFileStream("logfile.txt",FileMode.Create);returnProcessStuff(stm);//maythrowanexception}finally{if(stm!=null)stm.Close();}
由於上述情形相當普遍,C#提供一種特殊的語法進行相同的處理:
using(FileStreamstm=newFileStream("logfile.txt",FileMode.Create)){returnProcessStuff(stm);//maythrowanexception}
只要離開 using區塊,編譯器會自動釋放stm對象,Python的 with指令及也有類似的功能。
這些語言都有定義標準的異常情形及其出現的條件,程序設計者也可以丟出自己產生的異常(其實C++及Python的throw和catch支持絕大多數形態的對象)。
若某一個throw指令找不到對應的catch,控制流程會離開目前的副程序或控制結構,設法找到對應的catch,若到主程序的結尾還是找不到對應的catch,程序會強制退出,並顯示適當的錯誤信息。
AppleScript腳本語言可以將"try"區塊分為幾個部份,提供不同的信息及異常:
trysetmyNumbertomyNumber/0onerrorenumbernfromftotpartialresultprif(e="Can'tdividebyzero")thendisplaydialog"Youidiot!"endtry

流程控制延續性

延續性(Continuation)可以將目前子程序的運行狀態(包括目前的堆棧,區部變量及運行到的位置)存儲成一個對象,後續在其他子程序中可以利用此對象回到此子程序現在的運行狀態。
延續性一詞也可以指第一類延續性(first-class continuation),是指編程語言可以在任意時間點存儲目前的運行狀態,並在之後回到之前存儲的運行狀態。
程序需要分配空間給子程序用到的區域變量,而且在子程序退出時需發佈這些變量用到的空間。許多編程語言利用調用堆棧來存放這些變量,可以簡單快速的分配及發佈空間。也有一些編程語言使用動態存儲器分配來存儲變量,可以較靈活的分配變量,但分配及發佈空間較不方便。這二個架構下延續性的處理方式也會不同,各有其優點及缺點。
Scheme語言利用call-with-current-continuation函數(縮寫為call/cc) 可提供延續性功能。
參考資料
  • 1.    doi:10.1145/362929.362947
  • 2.    ACM, 9(5):366-371
  • 3.    ISBN 957717017X