位元詩人 [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 行。

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。