[Pascal] 程式設計教學:迭代控制結構 (Iteration Control Structure)

【分享文章】
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

    前言

    藉由迭代控制結構,程式設計者可以有效率地重覆執行特定程式碼,不需要重覆撰寫相同的代碼。本文介紹 Pascal 的迭代控制結構。

    while

    while 的使用方式

    while 敘述是通用的迭代控制結構,可用來反覆執行特定程式碼。以下是 while 敘述的 Pascal 虛擬碼:

    while condition do
      (* Run code here repeatedly only if condition is true. *)
    

    我們先用簡短的範例來看 while 敘述的用法:

    program main;
    uses
      SysUtils;
    
    var
      i : integer;
    
    begin
      i := 1;
    
      while i <= 10 do
      begin
        WriteLn(format('%d', [i]));
        i := i + 1;
      end;
    end.
    

    此範例程式以 i <= 10 做為迭代條件,只有在 i 符合條件時才會執行 while 敘述內的程式碼。

    由於此 while 敘述有多行指令,所以要放在區塊中。

    實際範例

    我們現在用一個長一點的例子來看使用 while 敘述的方式。該程式會生成這個的 ASCII 圖形:

    ********** **********
    *********   *********
    ********     ********
    *******       *******
    ******         ******
    *****           *****
    ****             ****
    ***               ***
    **                 **
    *                   *
    
    *                   *
    **                 **
    ***               ***
    ****             ****
    *****           *****
    ******         ******
    *******       *******
    ********     ********
    *********   *********
    ********** **********
    

    如果全部都用 WriteLn() 函式來繪製,這樣的練習就沒有意義了,所以我們會用 while 迴圈來實作。參考以下範例程式碼:

    program main;                                        (*  1 *)
    uses                                                 (*  2 *)
      SysUtils;                                          (*  3 *)
    
    var                                                  (*  4 *)
      i : word;                                          (*  5 *)
      j : word;                                          (*  6 *)
    
    begin                                                (*  7 *)
      i := 0;                                            (*  8 *)
    
      while (i <= 20) do                                 (*  9 *)
      begin                                              (* 10 *)
        j := 0;                                          (* 11 *)
    
        if i <= 10 then                                  (* 12 *)
          while j <= 20 do                               (* 13 *)
          begin                                          (* 14 *)
            if (10 - i <= j) and (j <= 10 + i) then      (* 15 *)
               Write(' ')                                (* 16 *)
            else                                         (* 17 *)
               Write('*');                               (* 18 *)
    
            j := j + 1;                                  (* 19 *)
          end                                            (* 20 *)
        else                                             (* 21 *)
          while j <= 20 do                               (* 22 *)
          begin                                          (* 23 *)
            if (i - 10 <= j) and (j <= 21 - i + 9) then  (* 24 *)
              Write(' ')                                 (* 25 *)
            else                                         (* 26 *)
              Write('*');                                (* 27 *)
    
            j := j + 1;                                  (* 28 *)
          end;                                           (* 29 *)
    
         WriteLn('');                                    (* 30 *)
         i := i + 1;                                     (* 31 *)
      end;                                               (* 32 *)
    end.                                                 (* 33 *)
    

    實作的部分在第 9 行至第 32 行。我們會把程式碼拆成上下兩段來做。上段位於第 12 行至第 20 行,下段位於第 21 至第 29 行,第 30 行及第 31 行則是共通的部分。

    在每個「像素」中,我們會跟據游標所在的位置決定繪出的字元是空白 ' ' 還是星號 '*'。在每行的尾端,還會輸出換行符號,把游標移到下一行。

    repeat

    repeat 敘述是 while 敘述的反向敘述,其虛擬碼如下:

    repeat
      (* Run code here until condition is true. *)
    until condition;
    

    實際的使用範例如下:

    program main;
    uses
      SysUtils;
    
    var
      i : word;
    
    begin
      i := 1;
    
      repeat
        WriteLn(format('%d', [i]));
        i := i + 1;
      until i > 10;
    end.
    

    由於 repeat 敘述本身帶有否定意味,程式碼比較不好閱讀。應該只把 repeat 敘述留在語意符合的情境,而且要避免用雙重否定的條件句來寫程式碼。

    for

    for 的使用方式

    for 敘述使用計數器來迭代,其虛擬碼如下:

    for counter := start to end do
      (* Run code here for fixed times. *)
    

    如果 for 敘述中有多行指令,同樣要以區塊包起來。

    以下是使用 for 敘述的簡短範例:

    program main;
    uses
      SysUtils;
    
    var
      i : word;
    
    begin
      for i := 1 to 10 do
      begin
        WriteLn(format('%d', [i]));
      end;
    end.
    

    由於 Pascal 的 for 敘述做得太嚴格了,其計數器的間距 (step) 是無法調整的。如果每次迭代的間距不為 1 的話,只得用 while 敘述來取代 for 敘述。

    實際範例

    在本節中,我們以實際範例來看 for 敘述的用法。該範例會繪製出以下的圖形:

              *
             ***
            *****
           *******
          *********
         ***********
        *************
       ***************
      *****************
     *******************
    *********************
     *******************
      *****************
       ***************
        *************
         ***********
          *********
           *******
            *****
             ***
              *
    

    以下是範例程式碼:

    program main;                        (*  1 *)
    uses                                 (*  2 *)
      SysUtils;                          (*  3 *)
    
    var                                  (*  4 *)
      i : word;                          (*  5 *)
      j : word;                          (*  6 *)
    
    begin                                (*  7 *)
      for i := 0 to 20 do                (*  8 *)
      begin                              (*  9 *)
        if i <= 10 then                  (* 10 *)
        begin                            (* 11 *)
          j := 0;                        (* 12 *)
          while j < 10 - i do            (* 13 *)
          begin                          (* 14 *)
            Write(' ');                  (* 15 *)
            j := j + 1;                  (* 16 *)
          end;                           (* 17 *)
    
          j := 0;                        (* 18 *)
          while j < 2 * i + 1 do         (* 19 *)
          begin                          (* 20 *)
            Write('*');                  (* 21 *)
            j := j + 1;                  (* 22 *)
          end;                           (* 23 *)
          WriteLn('');                   (* 24 *)
        end                              (* 25 *)
        else                             (* 26 *)
        begin                            (* 27 *)
          j := 0;                        (* 28 *)
          while j < i - 10 do            (* 29 *)
          begin                          (* 30 *)
            Write(' ');                  (* 31 *)
            j := j + 1;                  (* 32 *)
          end;                           (* 33 *)
    
          j := 0;                        (* 34 *)
          while j < 2 * (20 - i) + 1 do  (* 35 *)
          begin                          (* 36 *)
            Write('*');                  (* 37 *)
            j := j + 1;                  (* 38 *)
          end;                           (* 39 *)
    
          WriteLn('');                   (* 40 *)
        end;                             (* 41 *)
      end;                               (* 42 *)
    end.                                 (* 43 *)
    

    由於 Pascal 的 for 敘述的侷限,我們沒有全部都用 for 敘述來實作,而採用 for 敘述和 while 敘述混合的方式來實作。

    在本範例中,程式碼分為兩段。第一段位於第 10 行至第 25 行,第二段位於第 26 行至第 41 行。每個段落分別繪製半邊圖形。

    如冋先前的例子,我們會決定在每個「像素」中游標所要繪製的字元,可能是 ' ''*'。並在每行尾端用換行符號把游標移到下一行。

    break

    break 敘述的用途是提早結束迴圈。由於直接中斷掉迴圈沒有意義,故 break 敘述會搭配選擇控制結構來使用。以下是簡短的使用範例:

    program main;
    uses
      SysUtils;
    
    var
      i : word;
    
    begin
      for i := 1 to 10 do
      begin
        if i > 5 then
          break;
    
        WriteLn(format('%d', [i]));
      end;
    end.
    

    continue

    承上節,continue 敘述的用途是略過該次迭代剩下的敘述,直接進入下一輪迴圈。實際使用時會搭配選擇控制結構來使用。以下是簡短的使用範例:

    program main;
    uses
      SysUtils;
    
    var
      i : word;
    
    begin
      for i := 1 to 10 do
      begin
        if i mod 2 <> 0 then
          continue;
    
        WriteLn(format('%d', [i]));
      end;
    end.
    

    goto

    goto 敘述可在同函式內任意地跳躍到 label (標籤) 所在的位置。算是改變程式運行流程的方式中最自由的語法。但 goto 敘述易寫出難以維護的程式碼,而 Pascal 強調結構化的程式設計範式,故 Pascal 程式中甚少使用 goto 敘述。

    有少數情境適合使用 goto 敘述,像是在舊版 Pascal 程式碼中用來替代例外處理,或是用來釋放系統資源。

    實例:終極密碼

    在看完各種控制結構的用法後,我們用一個稍長的範例程式來展示如何使用控制結構。本節的範例程式是終極密碼,這是一個常見的小遊戲,很常當成程式設計的範例題目。

    以下是範例程式的程式碼:

    {$mode objfpc}                                           (*  1 *)
    program main;                                            (*  2 *)
    
    uses                                                     (*  3 *)
      SysUtils;                                              (*  4 *)
    
    var                                                      (*  5 *)
      min : integer;                                         (*  6 *)
      max : integer;                                         (*  7 *)
      answer : integer;                                      (*  8 *)
      guess : integer;                                       (*  9 *)
      hasGuess : boolean;                                    (* 10 *)
      input : string;                                        (* 11 *)
    
    begin                                                    (* 12 *)
      min := 1;                                              (* 13 *)
      max := 100;                                            (* 14 *)
    
      randomize;                                             (* 15 *)
      answer := random(max) + min;                           (* 16 *)
    
      while true do                                          (* 17 *)
      begin                                                  (* 18 *)
        hasGuess := false;                                   (* 19 *)
    
        while hasGuess <> true do                            (* 20 *)
        begin                                                (* 21 *)
          Write(                                             (* 22 *)
            Format(                                          (* 23 *)
              'Please input a number between %d and %d: ',   (* 24 *)
              [min, max]));                                  (* 25 *)
          ReadLn(input);                                     (* 26 *)
          try                                                (* 27 *)
            guess := StrToInt(input);                        (* 28 *)
            hasGuess := true;                                (* 29 *)
          except                                             (* 30 *)
            try                                              (* 31 *)
              StrToFloat(input);                             (* 32 *)
              WriteLn('Not a valid number: ', input);        (* 33 *)
            except                                           (* 34 *)
              WriteLn('Not a number: ', input);              (* 35 *)
            end;                                             (* 36 *)
    
            continue;                                        (* 37 *)
          end;                                               (* 38 *)
    
          if not((min <= guess) and (guess <= max)) then     (* 39 *)
          begin                                              (* 40 *)
            WriteLn(format('Invalid number: %d', [guess]));  (* 41 *)
            hasGuess := false;                               (* 42 *)
          end                                                (* 43 *)
        end;                                                 (* 44 *)
    
        if guess = answer then                               (* 45 *)
        begin                                                (* 46 *)
          WriteLn('You got it');                             (* 47 *)
          break;                                             (* 48 *)
        end                                                  (* 49 *)
        else if guess > answer then                          (* 50 *)
        begin                                                (* 51 *)
          WriteLn('Too large');                              (* 52 *)
          max := guess;                                      (* 53 *)
        end                                                  (* 54 *)
        else                                                 (* 55 *)
        begin                                                (* 56 *)
          WriteLn('Too small');                              (* 57 *)
          min := guess;                                      (* 58 *)
        end;                                                 (* 59 *)
      end;                                                   (* 60 *)
    end.                                                     (* 61 *)
    

    由於本範例程式有用到例外處理,故我們在第一行開啟 objfpc 模式。由於 Free Pascal 的編譯模式在不同檔案中可各自相異,有必要開啟特定模式時不用刻意不開啟。

    該程式在第 15 行呼叫 Randomize(),該函式會以系統時間做為亂數種子。然後,在第 16 行設置介於 1100 的隨機數做為答案。

    第 17 行至第 60 行是終極密碼的遊戲迴圈。在該迴圈的前半段負責接收使用者輸入,接收輸入後要檢查輪入是否合法 (valid)。輸入有可能是

    • 符合範圍的整數
    • 不符合範圍的整數
    • 浮點數
    • 非數字字串

    除了第一類以外,其他輸入都是非法的 (invalid)。當輸入是非法的,就要重跑下一輪迴圈。整個檢查輸入的程式位於第 19 行至第 44 行。

    在遊戲迴圈的後半段要負責判斷使用者的輸入,將輸入和預先生成的答案相比。當輸入和答案相同時,結束遊戲迴圈;反之,吐出提示訊息,繼續下一輪遊戲。後半段程式碼位於第 45 行至第 59 行。

    【分享文章】
    Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email
    【追蹤網站】
    Facebook Facebook Twitter Plurk