迴圈
迴圈是計算機科學運算領域的用語,也是一種常見的控制流程。迴圈是一段在程式中只出現一次,但可能會連續執行多次的程式碼。迴圈中的程式碼會執行特定的次數,或者是執行到特定條件成立時結束迴圈,或者是針對某一集合中的所有項目都執行一次。
在一些函數程式語言(例如Haskell和Scheme)中會使用遞歸或不動點組合子來達到迴圈的效果,其中尾部遞歸是一種特別的遞歸,很容易轉換為迭代。[1]
指定執行次數的迴圈(for loop)
大部份程式語言都提供迴圈的指令,可以依指定的次數重覆執行一段程式。
若指定的次數N小於1,程式語言會忽略整個迴圈不去執行,若指定的次數N為1,則迴圈只會執行一次。
在迴圈進行時,迴圈計數器也會隨著變化,大部份的程式語言可以允許迴圈計數器上數或是下數,每次的變化量可以是1或是其他不為0的數值。
FOR I = 1 TO N for I := 1 to N do begin xxx xxx NEXT I end; DO I = 1,N for ( I=1; I<=N; ++I ) { xxx xxx END DO }
在許多程式語言中,迴圈計數器要使用整數才能得到準確的結果。由於硬體的限制,在迴圈計數器使用浮點數時,結果可能會不符預期,如以下的迴圈
for X := 0.1 step 0.1 to 1.0 do
依其四捨五入的誤差、硬體及編譯器的差異,不一定會執行10次,可能只會執行9次。而且X的數值可能會有些誤差,不是預期的0.1, 0.2, 0.3, ..., 1.0。
指定條件的迴圈(while loop/doWhile loop)
大多數的程式語言都有指令,可以在特定條件成立時繼續迴圈的進行,或是特定條件不成立時繼續迴圈的進行,進行到特定條件成立為止。前者一般會標示while,後者一般會標示until。
其判斷條件可能在迴圈一開始就進行,或是在迴圈最後才進行。前者的迴圈不一定會執行,而後者1的迴圈至少會執行一次。
DO WHILE (test) repeat xxx xxx LOOP until test; while (test) { do xxx xxx } while (test);
指定集合的迴圈
許多程式語言支援一種特別的迴圈,可以針對一個陣列中的元素或是一個集合中的所有成員進行迴圈中的指令,包括Ada、D語言、Smalltalk、Perl、C#、Visual Basic、Ruby、Python、Java、JavaScript、Fortran 95等程式語言都有這類的迴圈結構:
someCollection do: [:eachElement |xxx]. foreach (item; myCollection) { xxx } foreach someArray { xxx } Collection<String> coll; for (String s : coll) {} foreach (string s in myStringCollection) { xxx } $someCollection | ForEach-Object { $_ } forall ( index = first:last:step... )
泛用迴圈結構
有些程式語言有泛用迴圈結構,可以用來表示指定次數或指定條件的迴圈,像C語言的for指令或是Common Lisp語言中的do指令都是這類的例子,不過為了程式的可讀性考量,在這些程式語言中還是儘量使用一些含義較明確的指令(如while指令)。
無窮迴圈
無窮迴圈一般會用在有一段程式需要永遠執行,或是該程式在沒有發生特殊事件(如故障)時需要永遠執行的場合,例如一個事件驅動的程式需要持續執行迴圈,處理發生的事件,直到使用者結束或中斷程式為止。
若在指定條件的迴圈中,其判斷條件用到的變數數值永遠不會改變,這種程式錯誤也會使得此迴圈變成無窮迴圈。
提早結束整個迴圈
當使用指定次數的迴圈查表時,會希望在查到需要的資料時就可以直接結束迴圈的進行,有些程式語言可以用break
或exit
的指令達到這様的功能,這些指令會結束這個迴圈,接著會執行迴圈後面的指令。若此迴圈在副程式中,也可以用return
中斷迴圈的進行, 同時離開副程式。
以下是Ada程式語言的一個範例,利用exit ... when...
的方式中提早結束迴圈。
with Ada.Text IO;
with Ada.Integer Text IO;
procedure Print_Squares is
X : Integer;
begin
Read_Data : loop
Ada.Integer Text IO.Get(X);
exit Read_Data when X = 0;
Ada.Text IO.Put (X * X);
Ada.Text IO.New_Line;
end loop Read_Data;
end Print_Squares;
Python支援一個特別的條件判斷式,可以根據最近使用迴圈是否曾用break
提早結束而做不同的處理,舉例如下:
for n in set_of_numbers:
if isprime(n):
print "Set contains a prime number"
break
else:
print "Set did not contain any prime numbers"
上例中的else
子句是for
迴圈的一部份,不是內層if
區塊的一部份。Python語言的for
迴圈及while
迴圈都支援else
子句,當迴圈沒有用break
提早結束時就會執行。
迴圈的特殊指令
有時在使用迴圈的程式中會希望在特定情形下跳過目前迴圈區塊的指令,回到迴圈開始執行下一個迴圈,一般這類的指令會命名為continue
、skip
或next
,其效果是提早結束這次迴圈的進行,繼續進行下一個迴圈,若此迴圈已經是最後一次執行,這類指令會結束迴圈的進行,繼續進行後續的指令。
像Perl及Ruby等程式語言有redo
指令,可以重新執行目前的迴圈,若在指定次數的迴圈中,其迴圈計數器的數值不會變化。Ruby程式語言有retry
指令,可以讓迴圈計數器回到初值,重新執行整個迴圈。
迴圈變式及迴圈不變式
迴圈變式是一個初值不為負的整數表示式,在每次執行迴圈時迴圈變式的數值需減少,但在正常的迴圈執行過程中迴圈變式的數值不會變成負值。迴圈變式用來確保迴圈會結束。
迴圈不變式是一個和迴圈有關的判斷式,在第一次進入迴圈之前,迴圈不變式的值需為真,在後續每一次執行迴圈時,其值也要為真。當迴圈正確的結束時,其終止條件和迴圈不變式都會成立。迴圈不變式可用來監控在迴圈進行時,某一指定性質的狀態。
像是Eiffel之類的程式語言本身就有支援迴圈變式及迴圈不變式,其他語言可能需要有附加元件才能支持此功能,例如Java就需要配合Java建模語言規範的loop statements (頁面存檔備份,存於網際網路檔案館)才能支持此機能。
不同語言的迴圈比較表
程式語言 | 條件判斷式 | 迴圈 | early exit | continuation | redo | retry | 正確性判斷機制 | ||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
迴圈啟始 | 迴圈中間 | 迴圈結尾 | 指定次數 | 指定集合 | 泛用 | 無窮[1] | 迴圈變式 | 迴圈不變式 | |||||
Ada | 是 | 是 | 是 | 是 | 只針對陣列 | 否 | 是 | 多層迴圈 | 否 | ||||
C | 是 | 否 | 是 | 否 [2] | 否 | 是 | 否 | 多層迴圈 [3] | 多層迴圈 [3] | 否 | |||
C++ | 是 | 否 | 是 | 否 [2] | 是 | 是 | 否 | 多層迴圈 [3] | 多層迴圈 [3] | 否 | |||
C# | 是 | 否 | 是 | 否 [2] | 是 | 是 | 否 | 多層迴圈 [3] | 多層迴圈 [3] | ||||
Common Lisp | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 多層迴圈 | 否 | ||||
Eiffel | 是 | 否 | 否 | 是 [10] | 是 | 是 | 否 | 一層迴圈 [10] | 否 | 否 | 否 [11] | 是 | 是 |
F# | 是 | 否 | 否 | 是 | 是 | 否 | 否 | 否 [6] | 否 | 否 | |||
FORTRAN 77 | 是 | 否 | 否 | 是 | 否 | 否 | 否 | 一層迴圈 | 是 | ||||
FORTRAN 90 | 是 | 否 | 否 | 是 | 否 | 否 | 是 | 多層迴圈 | 是 | ||||
FORTRAN 95及後續版本 | 是 | 否 | 否 | 是 | 陣列 | 否 | 是 | 多層迴圈 | 是 | ||||
Haskell | 否 | 否 | 否 | 否 | 是 | 否 | 是 | 否 [6] | 否 | 否 | |||
Java | 是 | 否 | 是 | 否 [2] | 是 | 是 | 否 | 多層迴圈 | 多層迴圈 | 否 | 非原生(non-native) [12] | 非原生(non-native) [12] | |
JavaScript | 是 | 否 | 是 | 否 [2] | 是 | 是 | 否 | 多層迴圈 | 多層迴圈 | 否 | |||
OCaml | 是 | 否 | 否 | 是 | 陣列及列表(list) | 否 | 否 | 否 [6] | 否 | 否 | |||
PHP | 是 | 否 | 是 | 否 [2] [5] | 是 [4] | 是 | 否 | 多層迴圈 | 多層迴圈 | 否 | |||
Perl | 是 | 否 | 是 | 否 [2] [5] | 是 | 是 | 否 | 多層迴圈 | 多層迴圈 | 是 | |||
Python | 是 | 否 | 否 | 否 [5] | 是 | 否 | 否 | 多層迴圈 [6] | 多層迴圈 [6] | 否 | |||
REBOL | 否 [7] | 是 | 是 | 是 | 是 | 否 [8] | 是 | 一層迴圈 [6] | 否 | 否 | |||
Ruby | 是 | 否 | 是 | 是 | 是 | 否 | 是 | 多層迴圈 [6] | 多層迴圈 [6] | 是 | 是 | ||
Standard ML | 是 | 否 | 否 | 否 | 陣列,列表(list) | 否 | 否 | 否 [6] | 否 | 否 | |||
Visual Basic .NET | 是 | 否 | 是 | 是 | 是 | 否 | 是 | 每種迴圈一層 | 每種迴圈一層 | ||||
Windows PowerShell | 是 | 否 | 是 | 否 [2] | 是 | 是 | 否 | ? | 是 |
- a 此項只考慮專用的無窮迴圈程式結構,因此
while (true)
和for ( ; ; )
都不算在內。 - a b c d e f g h C語言的
for (init; test; increment)
迴圈是一個通用的迴圈指令,累加量也不一定要為1。 - a b c 在C、C++及C#中,跳出多層迴圈可以用label和goto指令達到。
- a 在PHP 5中已支援 (頁面存檔備份,存於網際網路檔案館)配合物件的迴圈。
- a b c 指定次數的迴圈可以用重覆incrementing list或generator的方式來達到其效果,例如Python中的
range()
。 - a b c d e 可以用異常處理來跳出多層迴圈。
- a 沒有專門指令,但
while
指令可以用作此用途。 - a 沒有專門指令,但使用者可以定義通用迴圈指令。
- a 正在計劃的C++0x標準中已加入以範圍為基礎的 for 迴圈。標準模板庫(STL)中有
std::for_each
模板函數,可以對STL容器(container)的每個元素重複呼叫一個一元函數[3]。製作STL容器的巨集也可以達到類似的效果[4]。 - a 利用整數區間的迭代可以達到指定次數迴圈的效果, early exit可以用多增加一個exit的條件來達成。
- a Eiffel支援保留字
retry
,不過是用在契約式設計的異常處理,不是迴圈的流程控制指令。 - a 需要配合Java建模語言(JML)。
參考資料
- ^ arun_yh. 循环(loop), 递归(recursion), 遍历(traversal), 迭代(iterate)的区别 - arun_yh - 博客园. www.cnblogs.com. 博客園. [2017-03-09]. (原始內容存檔於2017-03-12) (中文(中國大陸)).
- ^ Meyer, Bertrand. Eiffel: The Language. Prentice Hall. 1991: 129~131.
- ^ for_each (頁面存檔備份,存於網際網路檔案館). Sgi.com. Retrieved on 2010-11-09.
- ^ Chapter 1. Boost.Foreach (頁面存檔備份,存於網際網路檔案館). Boost-sandbox.sourceforge.net (2009-12-19). Retrieved on 2010-11-09.