迴圈

From Wikipedia, the free encyclopedia

Remove ads

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

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

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

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

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

Remove ads

無限迴圈

Thumb
圖一:無限迴圈
内文:無限迴圈

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

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

loop
   迴圈內容;
end loop;

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

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

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

While 迴圈

Thumb
圖二:While 迴圈
内文:While 迴圈

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

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

Do-while 迴圈

Thumb
圖三:Do-while 迴圈

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

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

For 迴圈

Thumb
圖四: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 迴圈

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

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

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

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

Foreach 迴圈

Thumb
圖六: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」。
Remove ads

多重出口迴圈

Thumb
圖七:多重出口迴圈一例

電腦科學理論,多重出口迴圈(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 嗰陣被執行。

Remove ads

多層離開

喺電腦科學理論,多層離開(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]

Remove ads

其他相關流程

跳去下一個迭代

Thumb
圖八:跳去下一個迭代

喺某啲編程應用當中,編程員有時會想個程式跳過個迴圈嘅剩餘部份,直接進入下一個迭代(圖八);有啲程式語言有陳述式直接表達呢種意圖,例如 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

重做呢個迭代

Thumb
圖九:重做呢個迭代

又有時,編程員想即刻直接返返去個迴圈內容嘅開頭,連終止條件都唔檢查(圖九);Perl 同 Ruby 都有個叫 「redo」 嘅陳述式可以表達呢種流程,可以令個程式重新行過行緊嗰個迭代[19][20]。喺 Perl,呢種流程嘅其中一種常見嘅用途係喺處理輸入嗰陣用[19]

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

   {redo;}

重新做個迴圈

Thumb
圖十:重新做個迴圈

另外,有時編程員可能想個返返去個迴圈嘅最開頭,即係設定嗰步,重新開始過個迴圈(圖十)。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」 基本上就即係重新做過個迴圈。

Remove ads

變量同不變量

迴圈變量(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] 數值理應會維持不變。

書目

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.

Remove ads