前言
藉由迭代控制結構,程式設計者可以有效率地重覆執行特定程式碼,不需要重覆撰寫相同的代碼。本文介紹 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 行設置介於 1
至 100
的隨機數做為答案。
第 17 行至第 60 行是終極密碼的遊戲迴圈。在該迴圈的前半段負責接收使用者輸入,接收輸入後要檢查輪入是否合法 (valid)。輸入有可能是
- 符合範圍的整數
- 不符合範圍的整數
- 浮點數
- 非數字字串
除了第一類以外,其他輸入都是非法的 (invalid)。當輸入是非法的,就要重跑下一輪迴圈。整個檢查輸入的程式位於第 19 行至第 44 行。
在遊戲迴圈的後半段要負責判斷使用者的輸入,將輸入和預先生成的答案相比。當輸入和答案相同時,結束遊戲迴圈;反之,吐出提示訊息,繼續下一輪遊戲。後半段程式碼位於第 45 行至第 59 行。