控制流程(也稱為流程控制)是計(jì)算機(jī)運(yùn)算領(lǐng)域的用語,意指在程序運(yùn)行時(shí),個(gè)別的指令(或是陳述、子程序)運(yùn)行或求值的順序。不論是在聲明式編程語言或是函數(shù)編程語言中,都有類似的概念。
在聲明式的編程語言中,流程控制指令是指會(huì)改變程序運(yùn)行順序的指令,可能是運(yùn)行不同位置的指令,或是在二段(或多段)程序中選擇一個(gè)運(yùn)行。
基本概念
不同的編程語言所提供的流程控制指令也會(huì)隨之不同,但一般可以分為以下四種:
??繼續(xù)運(yùn)行位在不同位置的一段指令(無條件分支指令)。
??若特定條件成立時(shí),運(yùn)行一段指令,例如c語言的switch指令,是一種有條件分支指令。
??運(yùn)行一段指令若干次,直到特定條件成立為止,例如C語言的for指令,仍然可視為一種有條件分支指令。
??運(yùn)行位于不同位置的一段指令,但完成后會(huì)繼續(xù)運(yùn)行原來要運(yùn)行的指令,包括子程序、協(xié)程(coroutine)及延續(xù)性(continuation)。
??停止程序,不運(yùn)行任何指令(無條件的終止)。
中斷以及unix系統(tǒng)中的信號(hào)等較低級(jí)的機(jī)制也可以造成類似子程序的效果,不過通常這類機(jī)制會(huì)用來回應(yīng)外部的事件或是輸入。程序自修改因?yàn)槠鋵?duì)代碼的影響,也會(huì)影響控制流程,但多半不會(huì)有明顯的流程控制指令。
在機(jī)器語言或匯編語言中,流程控制是借由修改程序計(jì)數(shù)器數(shù)值來達(dá)到。一些中央處理器只支持條件分支(branch)或是無條件分支(有時(shí)會(huì)稱為jump)。
標(biāo)記
標(biāo)記是一個(gè)標(biāo)示在源代碼固定位置中的名稱或數(shù)字,其他位置的流程控制指令可以參考標(biāo)記的位置,運(yùn)行標(biāo)記位置所對(duì)應(yīng)的程序。標(biāo)記本身不影響程序的進(jìn)行,除了標(biāo)示位置外,對(duì)程序運(yùn)行沒有其他的作用。
有一些編程語言(像Fortran及BASIC等)利用行號(hào)作為標(biāo)記。行號(hào)是標(biāo)示在每一行程序最前面的自然數(shù),不一定要是連續(xù)的數(shù)字,在不受流程控制指令影響的情形下,程序會(huì)從最小的行號(hào)依序運(yùn)行,而流程控制指令需指定對(duì)應(yīng)的行號(hào)。以下是一個(gè)BASIC的例子:
在像是C及Ada等編程語言中,標(biāo)記是一個(gè)標(biāo)識(shí)符,一般出現(xiàn)在一行的最前面,后面會(huì)加一個(gè)冒號(hào)作為識(shí)別,以下是c語言的例子:
Success:printf("Theoperationwassuccessful.\n");
ALGOL 60語言同時(shí)支持整數(shù)(類似行號(hào))及標(biāo)識(shí)符的標(biāo)記(二者后面都要加上冒號(hào)),不過其他Algol語言幾乎都不支持整數(shù)的標(biāo)記。
Goto
goto指令(來自英文go和to的組合)是無條件流程控制指令中最基本的型式。一般在程序中會(huì)用以下的方式出現(xiàn)(指令大小寫可能會(huì)依編程語言而不同)
goto指令的效果是調(diào)整程序的控制流程,后續(xù)就運(yùn)行標(biāo)記位置的程序。
goto指令是許多的計(jì)算機(jī)科學(xué)家視為有害(consideredharmful)的指令,例如EdsgerWybeDijkstra提出了goto有害論。
子程序
子程序(subroutine)可以用許多不同的術(shù)語來表示,例如程序、函數(shù)(尤其是有傳回值時(shí))或是方法(特別是子程序?qū)儆谝粋€(gè)類的一部份)等。
子程序是是完成一項(xiàng)特定工作的代碼串行,其他程序可以將流程移轉(zhuǎn)到子程序中,運(yùn)行特定工作后再回到原來的程序,若程序中有許多部份都需要運(yùn)行一特定工作,利用子程序的方式可以利用一段程序達(dá)到上述的功能,可以減少代碼的長(zhǎng)度。
如今子程序也常用來使得程序更加的結(jié)構(gòu)化,例如可以將一些特殊的算法或特殊的數(shù)據(jù)訪問方式放在子程序中,和其他代碼隔離。子程序也是程序模塊的一種,若許多程序員共同開發(fā)一個(gè)程序,子程序也有助于其工作的分區(qū)及分工。
論述
1966年5月CorradoB?hm及GiuseppeJacopini在《CommunicationsoftheACM》發(fā)表論文,說明任何一個(gè)有g(shù)oto指令的程序,可以改為完全不使用goto指令的程序,goto指令可以用選擇指令(IFTHENELSE)及循環(huán)(WHILE特定條件DO特定程序)取代,可能會(huì)再多一些重復(fù)的代碼及額外的布林變量。后來的研究者已證明選擇指令也可以用循環(huán)取代,不過需要更多的布林變量。B?hm及Jacopini的論文說明程序可以完全不使用goto,但是在實(shí)務(wù)上大家不一定會(huì)想要這么進(jìn)行。
其他的研究說明若控制結(jié)構(gòu)只有一個(gè)進(jìn)入點(diǎn)(entry)及一個(gè)退出點(diǎn)(exit),這樣的程序會(huì)比其他型式的程序容易理解。因此這樣的程序可以像一個(gè)指令一樣放在程序的任何部份,不必?fù)?dān)心會(huì)破壞其結(jié)構(gòu),換句話說,這種程序是“可組成的”(composable)。
結(jié)構(gòu)
若一編程語言支持控制結(jié)構(gòu),控制結(jié)構(gòu)開始時(shí)多半都會(huì)有特定的關(guān)鍵字,以標(biāo)明是使用哪一種控制結(jié)構(gòu)。但只有部份編程語言在控制結(jié)構(gòu)退出時(shí)會(huì)有特定的關(guān)鍵字表示退出,因此可以依控制結(jié)構(gòu)退出時(shí)是否有特定關(guān)鍵字來將編程語言分為二類。
??沒有特定關(guān)鍵字的語言:ALGOL 60、C、C++、Haskell、Java、Pascal、Perl、PHP、PL/I、Python、WindowsPowerShell。這類語言需要有關(guān)鍵字可以將group程序指令together:
??Algol60及Pascal:Beginend
??C,C++,Java,Perl,PHP,andPowerShell:利用大括號(hào){...}
??PL/1:DO...END
??Python:利用縮進(jìn)(indentation)的層次,詳細(xì)內(nèi)容請(qǐng)參考Off-side規(guī)則
??Haskell:可以利用縮進(jìn)或大括號(hào),兩者可以混用
??有特定關(guān)鍵字的語言:Ada、Algol68、Modula-2(Modula-2)、Fortran77、Visual Basic,使用的特定關(guān)鍵字依編程語言而不同:
??Ada:其關(guān)鍵字為end+space+啟始控制結(jié)構(gòu)的關(guān)鍵字,如if...endif,loop...endloop
??Algol68,Mythryl:將啟始關(guān)鍵字反寫,如if...fi,case...esac
??Fortran77:其關(guān)鍵字為end+initialkeyword,如IF...ENDIF,DO...ENDDO
??Modula-2:不論何種控制結(jié)構(gòu),其關(guān)鍵字均為END
??Visual Basic:每種控制結(jié)構(gòu)均有各自的結(jié)尾關(guān)鍵字,如If...EndIf;For...Next;Do...Loop
條件判斷
條件判斷是依指定變量或表達(dá)式的結(jié)果,決定后續(xù)運(yùn)行的程序,最常用的是if-else指令,可以根據(jù)指定條件是否成立,決定后續(xù)的程序。也可以組合多個(gè)if-else指令,進(jìn)行較復(fù)雜的條件判斷。許多編程語言也提供多選一的條件判斷,例如c語言的switch-case指令。
循環(huán)
循環(huán)是指一段在程序中只出現(xiàn)一次,但可能會(huì)連續(xù)運(yùn)行多次的代碼。常見的循環(huán)可以分為二種,指定運(yùn)行次數(shù)的循環(huán)(如C語言的for循環(huán))以及指定繼續(xù)運(yùn)行條件(或停止條件)的循環(huán)(如C語言的while循環(huán))。
在一些函數(shù)編程語言(例如Haskell和Scheme)中會(huì)使用遞歸或不動(dòng)點(diǎn)組合子來達(dá)到循環(huán)的效果,其中尾部遞歸是一種特別的遞歸,很容易轉(zhuǎn)換為迭代。
非區(qū)部
有些編程語言會(huì)提供非區(qū)部的控制流程(non-localcontrolflow),會(huì)允許流程跳出目前的代碼,進(jìn)入一段事先指定的代碼。常用的結(jié)構(gòu)化非區(qū)部控制流程可分為條件處理(condition)、異常處理及延續(xù)性(Continuation)三種。
條件處理
PL/I編程語言中有22種標(biāo)準(zhǔn)的條件(如ZERODIVIDESUBSCRIPTRANGEENDFILE),可以在程序中設(shè)置,當(dāng)特定條件成立時(shí)需進(jìn)行的指令,程序設(shè)計(jì)者也可以定義自己的條件,并在程序中使用。
條件成立時(shí),只能設(shè)置一個(gè)需進(jìn)行的指令(類似未結(jié)構(gòu)化的if指令),大部份的應(yīng)用中,都會(huì)指定運(yùn)行g(shù)oto指令,跳到其他代碼運(yùn)行對(duì)應(yīng)的流程。
不過因?yàn)橛行l件處理的實(shí)現(xiàn)會(huì)增加許多代碼及運(yùn)行時(shí)間(特別SUBSCRIPTRANGE),所以許多程序設(shè)計(jì)者會(huì)盡量不使用條件處理。
條件處理的語法如下:
ON條件GOTOlabel
異常處理
有些編程語言可提供不需要使用GOTO的結(jié)構(gòu)化異常處理程序:
在try{...}的區(qū)塊中,若有異常情形時(shí),程序就會(huì)離開try的區(qū)塊,由后續(xù)的一個(gè)或多個(gè)catch子句判斷需運(yùn)行何種異常處理。在D、Java、C#及Python中,try{...}區(qū)塊中還可以加入一個(gè)finally子句,不管程序流程是否離開try{...}區(qū)塊,finally子句中的程序都一定會(huì)運(yùn)行,常用在當(dāng)程序退出處理時(shí),需要放棄一些外部資源(文件或數(shù)據(jù)庫鏈接)的情形下:
由于上述情形相當(dāng)普遍,C#提供一種特殊的語法進(jìn)行相同的處理:
只要離開using區(qū)塊,編譯器會(huì)自動(dòng)釋放stm對(duì)象,Python的with指令及也有類似的功能。
這些語言都有定義標(biāo)準(zhǔn)的異常情形及其出現(xiàn)的條件,程序設(shè)計(jì)者也可以丟出自己產(chǎn)生的異常(其實(shí)C++及Python的throw和catch支持絕大多數(shù)形態(tài)的對(duì)象)。
若某一個(gè)throw指令找不到對(duì)應(yīng)的catch,控制流程會(huì)離開目前的副程序或控制結(jié)構(gòu),設(shè)法找到對(duì)應(yīng)的catch,若到主程序的結(jié)尾還是找不到對(duì)應(yīng)的catch,程序會(huì)強(qiáng)制退出,并顯示適當(dāng)?shù)腻e(cuò)誤信息。
AppleScript腳本語言可以將"try"區(qū)塊分為幾個(gè)部份,提供不同的信息及異常:
延續(xù)性
延續(xù)性(Continuation)可以將目前子程序的運(yùn)行狀態(tài)(包括目前的堆棧,區(qū)部變量及運(yùn)行到的位置)存儲(chǔ)成一個(gè)對(duì)象,后續(xù)在其他子程序中可以利用此對(duì)象回到此子程序現(xiàn)在的運(yùn)行狀態(tài)。
延續(xù)性一詞也可以指第一類延續(xù)性(first-classcontinuation),是指編程語言可以在任意時(shí)間點(diǎn)存儲(chǔ)目前的運(yùn)行狀態(tài),并在之后回到之前存儲(chǔ)的運(yùn)行狀態(tài)。
程序需要分配空間給子程序用到的區(qū)域變量,而且在子程序退出時(shí)需發(fā)布這些變量用到的空間。許多編程語言利用調(diào)用堆棧來存放這些變量,可以簡(jiǎn)單快速的分配及發(fā)布空間。也有一些編程語言使用動(dòng)態(tài)存儲(chǔ)器分配來存儲(chǔ)變量,可以較靈活的分配變量,但分配及發(fā)布空間較不方便。這二個(gè)架構(gòu)下延續(xù)性的處理方式也會(huì)不同,各有其優(yōu)點(diǎn)及缺點(diǎn)。
Scheme利用call-with-current-continuation函數(shù)(縮寫為call/cc)可提供延續(xù)性功能。
參考資料 >