跳去內容

迴圈

出自維基百科,自由嘅百科全書
(由程式迴圈跳轉過嚟)

迴圈[註 1]粵拼wui4 hyun1英文loop)係一種控制流程。喺程式入面,如果部電腦做嘢嘅步驟喺某一步係返轉頭去之前嘅其中一個步驟,兩個步驟之間就構成一個迴圈;迴圈入面嘅嘢會重覆做,每做次叫一個迭代iteration)。

結構上,最簡單嘅迴圈係無條件返轉頭(叫無限迴圈),但係一般嘅迴圈都只會有條件噉返轉頭;呢種決定返唔返轉頭嘅條件叫終止條件(exit condition)。個程式會按照個迴圈所指定嘅條件,重複噉執行迴圈入面嘅嘢若干次,直至個條件達到為止。

低級程式語言,點樣整迴圈原則上喺點整都得,但係實際上,迴圈嘅結構通常都有規律,夾埋得幾大類,呢啲類型通常都係用高級程式語言用嘅有關關鍵字嚟做名,最常見嘅迴圈包括所謂 For 迴圈同所謂 While 迴圈,仲有其他,不過以正統嘅結構化編程嘅角度嚟講,最基本嘅迴圈只係得 While 迴圈,任何其他類型嘅迴圈都可以寫成 While 迴圈。

無論係用高級定低級程式語言,迴圈入面嘅都係雖然淨係講咗一次,但會喺個程式行唔止一次。喺方便嘅角度嚟睇,用迴圈可以令到程式編寫方便好多,因為同一段要用多次嘅碼淨係需要打一次[1],不過其實有好多嘢係唔用迴圈根本做唔到嘅。

廿一世紀常用嘅程式語言冚唪唥都有語法寫迴圈(通常係種係種陳述式),而高階程式語言通常亦有多過一種迴圈,例如 C 程式語言C++、同 C# 等都支援好幾種迴圈,呢幾隻語言都有一種陳述式可以直接表達差唔多所有種類嘅迴圈[2]

無限迴圈

[編輯]
圖一:無限迴圈
内文:無限迴圈

無限迴圈infinite loop)係指一個永遠都行唔完嘅迴圈,亦即係最少同最多都係做無限次;原因可能係因為個迴圈根本冇終止條件、有一個冇可能達得到嘅終止條件、又或者有一啲會搞到個迴圈重新啟動嘅陳述式。無限迴圈好多時係錯誤,可以搞到部電腦輕機

不過,無限迴圈其實有佢嘅用途,亦即係有可能係特登,某啲程式語言亦有語法直接表達無限迴圈,例如,喺 Ada

loop
   迴圈內容;
end loop;

表示概念上,中間嘅 「迴圈內容」 會做無限次(實際上,「迴圈內容」 可能包括一個或以上嘅終止條件)。譯成目前常見嘅 C 語系語法通常寫成

for (;;) {
   迴圈內容;
}

喺結構上,無限迴圈可以話係最簡單,但係喺結構化編程嘅角度,最基本嘅迴圈並唔係佢,而係 While 迴圈。

While 迴圈

[編輯]
圖二:While 迴圈
内文:While 迴圈

所謂 While 迴圈while loop;喺 Pascal 又叫 「While-do 迴圈」[3])係指終止條件喺迴圈頂部嘅迴圈,最少做零次,最多做無限次;因為通常係用關鍵字 「while」 表達,所以咁叫。While 迴圈係喺正統嘅結構化編程入面最基本嘅迴圈;廿一世紀初多數程式語言都有語法直接表達。

形式(C 語系語法)
while (終止條件嘅相反) {
   迴圈內容;
}
實際流程(Ada 語法;圖二)
loop
exit when 終止條件;
   迴圈內容;
end loop;

Do-while 迴圈

[編輯]
圖三:Do-while 迴圈

所謂 Do-while 迴圈do-while loop)係指終止條件喺迴圈底部嘅迴圈,最少做一次,最多做無限次;因為有啲語言表示呢種迴圈嘅語法係喺頂有個關鍵字 「do」,喺底又有個關鍵字 「while」,所以咁叫。不過有好多受歡迎嘅程式語言(例如 Python[4])都冇語法直接表達 do-while 迴圈。

形式(C 語系語法)
do {
   迴圈內容;
} while (終止條件嘅相反);
實際流程(Ada 語法;圖三)
loop
   迴圈內容;
exit when 終止條件;
end loop;

For 迴圈

[編輯]
圖四:For 迴圈

所謂 For 迴圈for loop)係指語法通常用 「For」 呢個關鍵字表示嘅迴圈;喺某個意義上,幾乎所有程式語言都有呢種迴圈,但係唔同嘅程式語言,用 「For」 呢個關鍵字表示嘅迴圈其實唔止一種,而係可以分成兩大類(其實仲有第三類,但係第三類通常叫 「Foreach 迴圈」)。

本來嘅 For 迴圈

[編輯]

喺早期嘅程式語言(例如 FortranBASIC),For 迴圈(喺 Fortran 又叫 「Do 迴圈」[5])一定係由一個數值數到第個數值,中間通常係跳 1,但係編程員可以指定其他數值。例如,喺 Applesoft BASIC(略去行號),

FOR I = 1 TO 100 STEP 2
   迴圈內容
NEXT

表示中間嘅 「迴圈內容」 會做 50 次​​首先設定變數 I  係 1(圖中嘅 「設定」),每個迭代嘅迴圈內容做完之後加 2(圖中嘅 「更新」),直到符合終止條件 「I  大過 100」 為止(圖中嘅 「終止條件」)。譯成目前常見嘅 C 語系語法大概即係[註 2]

for (i = 1; i <= 100; i += 2) {
   迴圈內容;
}

除咗早期嘅語言之外,一啲較為後期嘅語言(例如 PostScript)都係呢種 For 迴圈。注意關鍵字未必係喺頭;例如,因為 PostScript 係種堆疊為本嘅編程語言,佢個 「for」 關鍵字係喺尾嘅[註 3]

C 語系嘅 For 迴圈

[編輯]
圖五:冇終止條件嘅 C 語系 For 迴圈,其實係一種無限迴圈
内文:For 迴圈

C 將本來嘅 For 迴圈廣義化,將控制流程分成三部分​​設定、終止條件同更新,三樣嘢全部都可以係任何表達式,任何一步嘅表達式亦都可以省略,表示嗰步唔做任何嘢。呢種迴圈最少做零次,最多做無限次。

注意,如果呢種 For 迴圈省略咗終止條件,其實即係無限迴圈,只係多咗設定同更新呢兩步。

形式(C 語系語法,有終止條件)
for (設定 ; 終止條件嘅相反; 更新 ) {
   迴圈內容;
}
實際流程(Ada 語法;圖四)
設定;
loop
exit when 終止條件;
   迴圈內容;
   更新;
end loop;
形式(C 語系語法,冇終止條件)
for (設定 ;; 更新 ) {        
   迴圈內容;
}
實際流程(Ada 語法;圖五)
設定;
loop
   迴圈內容;
   更新;
end loop;

Foreach 迴圈

[編輯]
圖六:Foreach 迴圈嘅流程結構同廣義嘅 For 迴圈其實完全冇分別​​設定:設定迭代器,攞第一個元素;更新:攞下一個元素;終止條件:目前嘅元素冇效

所謂 Foreach 迴圈for each loop)其實即係用迭代器iterator);咁叫係因為某啲語言(例如 Perl)可以用 「foreach」 呢個關鍵字嘅語法表達呢種迴圈,不過喺某啲有直接支援嘅語言,實際上可能其實係用 「for」 關鍵字或者可以用 「for」 關鍵字,而未必係真係用 「foreach」。喺流程結構上佢同廣義嘅 For 迴圈其實完全冇分別,有啲人亦當佢係 For 迴圈嘅一種。

所謂迭代器,即係概念上有件物件係一個陣列,而呢種概念上嘅物件,喺概念上有某種方法,按某種次序(通常係索引值嘅順序),將陣列入面嘅數值全部逐一處理。可以用乜嘢陣列就視乎語言,喺某啲語言(例如 Lua),就算係關聯陣列都冇問題[註 4]

舉個例,假如有個陣列,入面係字串,假設數值順序係 「vimal」、「sonoo」、「ratan」。如果要將呢三個數值 show 哂出嚟,用 Perl 可以咁寫:

my @list = ('vimal', 'sonoo', 'ratan'); # 整一個陣列,個名叫 「list」,入面順序擺 「vimal」、「sonoo」、「ratan」。
foreach my $s (@list) { # 逐一處理陣列變數 list 入面嘅每個元素,每個迭代處理嘅元素擺入純量變數 s,然後...
   printf "%s\n", $s; # show 個元素出嚟睇。
}

譯成 Java 會寫成類似係咁[6]

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

   for(String s:list){ // 逐一處理變數 list 嘅每個元素,每個迭代處理嘅元素擺入變數 s,然後...
     System.out.println(s); // show 個元素出嚟睇。
   }
 } // 呢段碼會喺 output 嗰度 show 出「vimal sonoo ratan」。

多重出口迴圈

[編輯]
圖七:多重出口迴圈一例

電腦科學理論,多重出口迴圈(multi-exit loop[註 5];有啲人叫 「提早離開迴圈」,early-exit loop)係指迴圈頂部同底部之外有其他地方插入咗終止條件嘅迴圈[7],概念上可以睇成無限迴圈入面加插一個或以上終止條件,例如好似係噉(圖七):

loop
   迴圈內容甲;
exit when 終止條件乙;
   迴圈內容丙;
exit when 終止條件丁;
   迴圈內容戊;
   ⋮
end loop;

換句話講,有陣時,個編程員會想個程式乎合某個終止條件嗰陣即刻停止個迴圈,但係個位又唔喺頂,又唔喺底。例如假設想用個 While 迴圈係噉耖一柞數據,由頭耖到落尾,喺咁嘅情形之下,理想嘅話,個程式應該係搵到數據就即刻離開個迴圈,而唔係繼續耖柞數據。

直接表示呢種 「即刻離開」 嘅語法其實唔係新嘢,而係喺20世紀一早已經有,例如 C 嘅 「break」、Ada 嘅 「exit」 同 Perl 嘅 「last」 等等,其中 Ada 同 Perl 可以寫到個關鍵字喺陣述式開頭(Ada:「exit when 終止條件 ;」;Perl:「last if 終止條件 ;」 或者 「last unless 終止條件嘅相反 ;」[註 6]),等容易睇到嗰行係迴圈出口。

喺完全冇語法直接表示呢個概念嘅語言,呢種做法有時可以用 Goto 表示。

舉個例,好似係以下呢段用 C 寫嘅碼噉,因為終止條件喺迴圈內容同變數更新中間,離開條件又唔喺頂(唔係 While 迴圈),又唔喺底(唔係 Do-while 迴圈),想即刻離開就要用 「break」[註 7][8]

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

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

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

離開決策

[編輯]

某啲程式語言會將迴圈分類,有喺實際執行嗰陣用到提早離開語法(即係例如源碼寫咗 「break」,而執行途中又真係有 break;亦即係 while、do-while、for 等等結構上有 break 嘅語法全部唔算數)嘅係一類,冇喺實際執行途中提早離開嘅係另外一類,呢啲語言會有語法,等編程員可以判斷某個迴圈喺實際執行嘅途中有冇提早離開,例如以下呢段 Python 碼[9]

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 嗰陣被執行。

多層離開

[編輯]

喺電腦科學理論,多層離開(multi-level exit[註 8])係指當迴圈入面又有迴圈嗰陣(即係所謂 「嵌套住嘅迴圈」[暫譯]nested loop),入面嘅迴圈可以按佢嘅終止條件直接離開出面嘅迴圈[10];呢種做法有時可以減少重覆嘅源碼[11]

有少量嘅程式語言有語法直接表達呢種流程,其中一種做法係喺出面迴圈嘅碼塊加標籤,咁入面嘅迴圈就可以直接指定出面迴圈嘅標籤,達致多層離開[10];例如 Perl 就有呢種語法。喺關鍵字係 「break」 啲語言,有啲人會叫呢種流程控制做 「multi-level break」。

喺冇語法直接表達呢種流程嘅語言,有時可以用 goto 表達[11]。但係如果唔想用 goto,想表達呢種流程會比較撈絞[12][13][14],原因係喺好多程式語言入面(例如 Python),表達 「即刻離開」 嘅語法(例如 Python 嘅 「break」)衹會終止一層迴圈,即係話如果有一個嵌套住嘅迴圈嗰度做即刻離開,外層嗰個迴圈仍然會如常噉繼續行。

喺冇直接支援嘅情形下,另外一個常見嘅做法係將個迴圈擺喺一個子程式當中,再喺個子程式裏面用 「return」(終止個子程式)嚟離開成個(包含咗一大柞迴圈嘅)子程式。

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

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

其他相關流程

[編輯]

跳去下一個迭代

[編輯]
圖八:跳去下一個迭代

喺某啲編程應用當中,編程員有時會想個程式跳過個迴圈嘅剩餘部份,直接進入下一個迭代(圖八);有啲程式語言有陳述式直接表達呢種意圖,例如 PerlRuby 嘅 「next」 同 C 語系嘅 「continue」。呢啲陳述式一定要搭配迴圈使用,會結束目前嗰個迭代,直接跳去行下一個迭代,而如果個迭代係最後一個,呢個陳述式會直接終止個迴圈。

例如係以下呢段 C 碼[18]

# 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」 嘅陳述式可以表達呢種流程,可以令個程式重新行過行緊嗰個迭代[19][20]。喺 Perl,呢種流程嘅其中一種常見嘅用途係喺處理輸入嗰陣用[19]

濫用呢種流程隨時可以導致代碼難讀化;舉個極端嘅例子,喺 Perl,碼塊算係行一次嘅迴圈[21],所以下面嘅一行其實係個無限迴圈,但係點睇都唔會睇得出:

   {redo;}

重新做個迴圈

[編輯]
圖十:重新做個迴圈

另外,有時編程員可能想個返返去個迴圈嘅最開頭,即係設定嗰步,重新開始過個迴圈(圖十)。Ruby 曾經可以用一個叫 「retry」 嘅陳述式表達呢種流程,不過呢種語法只係支援咗好短嘅時間,而且好少人用,已經冇咗好耐[20]

喺 Perl,因為碼塊係行一次嘅迴圈[21],呢種流程原則上可以用有標記參數嘅 「redo」 表達:

A: {
   for (my $i = 1; $i < 10; $i += 1) {
      printf "%d\n", $i;
      redo A if rand(5) < 1;
   }
}

喺上面嘅例,因為標記做 A 嘅碼塊喺迴圈外面,「redo A」 基本上就即係重新做過個迴圈。

變量同不變量

[編輯]

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

  • 迴圈變量指一個數值會隨住迭代而減少嘅變數,設嚟監察個迴圈運作係咪如常。例如係喺以下呢段 Whiley 碼當中,|items| - i 會係一個啱用嘅迴圈變量[23]
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 碼[24]
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] 數值理應會維持不變。

[編輯]
  1. McShaffry, M. (2014). Game coding complete. Nelson Education. p. 34.
  2. Definition of a Loop. ThoughtCo.
  3. Tam, James. "Loops in Pascal" (PDF) (加拿大英文). University of Calgary. p. 5. 喺2025年4月7號搵到.
  4. Python Do While Loop 互聯網檔案館歸檔,歸檔日期2019年9月5號,..
  5. Hargrove, Paul H.; Whitlock, Sarah T.; Boman, Erik (1997) [1995]. "Loops". Fortran 77 Tutorial (美國英文). Stanford University. 喺2025年4月7號搵到.
  6. Java For-each Loop | Enhanced For Loop[失咗效嘅鏈].
  7. “CS343” 2022, p. 1.
  8. 8.0 8.1 "C – break statement in C programming". 歸檔時間2019年9月6號.{{cite web}}: CS1 maint: unfit URL (link).
  9. Python break and continue 互聯網檔案館歸檔,歸檔日期2019年9月6號,..
  10. 10.0 10.1 “CS343” 2022, p. 2.
  11. 11.0 11.1 “CS343” 2022, p. 4.
  12. comp.lang.c FAQ list. "Question 20.20b".
  13. Breaking out of two loops 互聯網檔案館歸檔,歸檔日期2019年9月6號,..
  14. David Anthony Watt; William Findlay (2004). Programming language design concepts. John Wiley & Sons. pp. 215–221.
  15. [Python-3000 Announcing PEP 3136] 互聯網檔案館歸檔,歸檔日期2019年9月6號,., Guido van Rossum.
  16. Kozen, Dexter (2008). The Böhm–Jacopini Theorem Is False, Propositionally (PDF). Lecture Notes in Computer Science. 5133. pp. 177–192.
  17. 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.
  18. C break and continue 互聯網檔案館歸檔,歸檔日期2019年9月6號,..
  19. 19.0 19.1 "Perl syntax: declarations, statements, comments". Perl Programmers Reference Guide (英文). 喺2025年4月10號搵到.
  20. 20.0 20.1 Flanagan, David; Matsumoto, Yukihiro (2008). "Altering Control Flow". The Ruby Programming Language: Everything You Need to Know (英文). O'Reilly Media, Inc. 喺2025年4月9號搵到.
  21. 21.0 21.1 "redo". Perldoc Browser (英文). 喺2025年4月10號搵到.
  22. Meyer, Bertrand (1991). Eiffel: The Language. Prentice Hall. pp. 129–131.
  23. Winskel, Glynn (1993). The Formal Semantics of Programming Languages: An Introduction. Massachusetts Institute of Technology. pp. 32–33, 174–176.
  24. David Gries. "A note on a standard strategy for developing loop invariants and loops." Science of Computer Programming, vol 2, pp. 207–214. 1984.

[編輯]
  1. 以前又叫迴路,見駱德廉 無日期,頁 28
  2. 概念上係噉,實際上唔完全係;Applesoft 嘅迴圈出口喺迴圈底,唔係喺迴圈頂。
  3. 上面嘅例子,譯成 PostScript 會寫成 「1 2 100 { 迴圈內容 } for」,呢度嘅迴圈內容亦應該喺每個迭代都攞走堆疊上面對應嗰個迭代嘅一個數值。
  4. 技術上,Lua 嘅所有陣列都係關聯陣列;普通嘅陣列只係所有索引值都係正整數嘅關聯陣列。
  5. 原文見:“CS343” 2022, p. 1
  6. Perl 嘅 「unless」 即係 「if」 嘅相反,如果乎合指定條件就唔執行。
  7. 原則上,喺結構化編程嘅角度,冇所謂 「要用 break」 呢種事;任何控制流程都可以用 if 同 while 寫得出嚟,分別只係意思表達得清唔清楚、使唔使用多啲字、使唔使用多幾個變數等等。
  8. 原文見:“CS343” 2022, pp. 2, 11

書目

[編輯]