前言
預設情形下,電腦程式會逐行執行敘述。透過控制流程可以改變敘述執行的順序。本文介紹 PHP 的控制結構。
if 敘述
if 敘述是基本的選擇控制結構。以下是 if 的虛擬碼:
if (condition) {
statement;
...
}
只有在 condition 符合時,會逐一執行 if 區塊內的 statement。反之,則略過該區塊。
if 敘述可加上選擇性的 else 敘述。這時候 if 敘述變成二元敘述:
if (condition) {
statement;
...
}
else {
statement;
...
}
當 condition 符合時,執行 if 區塊內的 statement。反之,執行 else 區塊內的 statement。
承上,還可以加上選擇性的 else if 敘述,成為多元敘述:
if (condition) {
statement;
...
}
else if (condition) {
statement;
...
}
else {
statement;
...
}
我們先前提過,else 敘述是選擇性的。所以下列虛擬碼也是合法的:
if (condition) {
statement;
...
}
else if (condition) {
statement;
...
}
看過虛擬碼,來看實際的例子:
<?php
# Get a random number between 1 and 10.
$number = rand(1, 10);
# Check whether the number is even or odd.
if (0 == $number % 2) {
echo "$number is even";
}
else {
echo "$number is odd";
}
$number 是介於 1 到 10 之間的正整數,該數使用 PHP 內建的 rand 函式運算而得。
這裡使用二元的 if 敘述來判斷 $number 是奇數還是偶數,將判斷結果印到終端機。
switch 敘述
switch 敘述是一個特化的選擇控制結構。其虛擬碼如下:
switch (value) {
case a:
statement;
...
break;
case b:
# Fallthrough
case c:
statement;
...
break;
default:
statement;
...
break;
}
switch 敘述比較的對象是 value,根據 value 的值執行不同的指令。
當 value 為 a 時,執行 a 區塊內的 statement。在 a 區塊的敘述走完時,會碰到 break 敘述,這時會跳開整個 switch 敘述。
當 value 為 b 時,沒有碰到 break 敘述,故會繼續往 c 區塊走。在 c 區塊完成時,會碰到 break 敘述,跳離 switch 敘述。這樣的行為稱為 fallthrough 。用得好是特性,用不好變臭蟲。
當 value 為 c 時,行為類似 a,不重述。
當 value 不屬於 a、b、c 任一值時,會執行 default 區塊內的敘述。default 區塊是選擇性的,不需要的話可以省略。
看過虛擬碼,來看實際的 PHP 程式:
<?php
# Get day of week.
$dayOfWeek = date("w");
# Emit corresponding description of
# current day of week.
switch ($dayOfWeek) {
case 6:
case 7:
echo "Weekend";
break;
case 5:
echo "Thank God. It's Friday";
break;
case 3:
echo "Hump day";
break;
default:
echo "Week";
break;
}
$dayOfWeek 的值是一星期的某一天,該值由 date 函式求得。
使用 switch 敘述來判斷 $dayOfWeek 的值,根據其值在終端機印出相對應的訊息。
理論上,所有的 switch 敘述都可以用 if 敘述改寫,但適度使用 switch 敘述,程式碼會更簡潔。
while 敘述
while 是基本的迭代控制結構,可以重覆執行一段程式碼,就不需要撰寫重覆的程式碼。以下是 while 敘述的虛擬碼:
while (condition) {
statement;
...
}
每當要進行一次迭代時,會先檢查 condition 是否為真。當 condition 為真時,執行該 while 區塊內的指令。然後再重新進行下一輪的迭代。
只要 condition 一直為真,while 敘述就會持續執行。在偶然情形下,會寫出永遠跳不出 while 敘述的程式,這種臭蟲稱為無窮迴圈 (infinite loop)。
看完虛擬碼,來看一下實際的程式:
<?php
$i = 1;
while ($i <= 10) {
echo $i, "\n";
++$i;
}
在這個 while 敘述中,$i 每次迭代會遞增 1。當 $i 超過 10 時,while 敘述的條件不符合,即跳離此迴圈。實際的效果為逐一印出 1 至 10 的數字。
for 敘述
for 敘述是使用計數器 (counter) 的迭代控制結構,其虛擬碼如下:
for (start; end; next) {
statement;
...
}
在 start 子區塊中,會將一至多個計數器初始化。在計數器仍符合 end 子區塊的條件時,會繼續該輪迭代。每輪迭代後,會在 next 子區塊遞增或遞減計數器。實際執行的指令為大的 for 區塊內的程式碼。
只看這樣敘述會覺得有點抽象,改看一下實際的程式:
<?php
for ($i = 1; $i <= 10; ++$i) {
echo $i, "\n";
}
這個 for 敘述使用的計數器為 $i。該計數器的起始值為 1。當 $i 小於等於 10 時,for 會繼續進行。每輪 $i 遞增 1。
實際上,所有的 for 敘述都可以用 while 敘述改寫。這裡的例子改寫成 while 敘述剛好是前一節的例子,讀者可以交互對照著看。當語意上有明確的計數器時,用 for 敘述會比用等效的 while 敘述來得易讀。
foreach 敘述
foreach 敘述和陣列 (array) 或迭代 (iterable) 相關。留在說明陣列時一併介紹。
改變迴圈行進的敘述
本節說明可在迴圈中改變迴圈行進的敘述。
break 敘述
使用 break 敘述,可以提早中斷迴圈。以下是實際的範例程式:
<?php
for ($i = 1; $i <= 10; ++$i) {
if ($i > 5) {
break;
}
echo $i, "\n";
}
這個範例類似於先前的 for 敘述。但 $i 大於 5 時,會觸發 break 敘述,提早離開此迴圈。實際的行為是從 1 至 5 印出數字五次。
continue 敘述
使用 continue 敘述可以略過該輪迭代,繼續下一輪迭代。以下是實際的程式:
<?php
for ($i = 1; $i <= 10; ++$i) {
if (0 == $i % 2) {
continue;
}
echo $i, "\n";
}
這個範例類似於先前的 for 敘述。但 $i 為偶數 (對 2 取餘數為 0) 時,會觸發 continue 敘述,略去該輪迴圈剩下的敘述。實際的行為是印出 1、3、5、7、9 五個奇數。
goto 敘述
goto 敘述可以不按照程式原定的執行順序,跳到特定標籤 (label) 所在的敘述。以下短例使用 goto 敘述:
<?php
# Jump to `label`.
goto label;
# The statement is skipped.
die("It won't die");
label:
echo "It will show", "\n";
在大部分情境下,使用 goto 敘述是不良實務。但適當地使用 goto 敘述,可以讓程式碼更簡潔,像是在釋放系統資源 (system resources) 時。
(範例) 終極密碼
本文先前的範例程式偏短,較難感受控制結構在程式中的用途。本節展示一個稍長的範例,用來展示各種控制結構的使用方式。
這個 PHP 程式是終極密碼 (猜數字)。為了簡化程式碼,這裡寫成終端機的版本。以下是執行該程式的過程:
$ php guessNumber.php
Input a number between 1 and 100: 50
Too small
Input a number between 50 and 100: 75
Too large
Input a number between 50 and 75: 67
Too large
Input a number between 50 and 67: 59
Too large
Input a number between 50 and 59: 55
Too small
Input a number between 55 and 59: 57
You guess right
該程式的完整程式碼如下。我們會在下文逐一說明:
<?php /* 1 */
# A simple number guessing game. /* 2 */
# Set bound value of an answer. /* 3 */
$min = 1; /* 4 */
$max = 100; /* 5 */
# Get a random number between `MIN` and `MAX`. /* 6 */
$answer = rand($min, $max); /* 7 */
# Set current game status. /* 8 */
$gameOver = false; /* 9 */
while (!$gameOver) { /* 10 */
$guess = null; /* 11 */
# Keep to prompt user until a valid guess is available. /* 12 */
while (is_null($guess)) { /* 13 */
# Install php-readline extension for this command. /* 14 */
$input = readline( /* 15 */
"Input a number between {$min} and {$max}: " /* 16 */
); /* 17 */
# Trim spaces and EOL. /* 18 */
$input = trim($input); /* 19 */
if (!preg_match("/^[+-]?\d+$/s", $input)) { /* 20 */
echo "Invalid data: {$input}" . "\n"; /* 21 */
continue; /* 22 */
} /* 23 */
# Convert input into an integer. /* 24 */
$input = (int) $input; /* 25 */
if (!($min <= $input && $input <= $max)) { /* 26 */
echo "Integer out of range: {$input}" . "\n"; /* 27 */
continue; /* 28 */
} /* 29 */
$guess = $input; /* 30 */
} /* 31 */
# Test `$guess` against `$answer`. /* 32 */
if ($answer < $guess) { /* 33 */
echo "Too large" . "\n"; /* 34 */
$max = $guess; /* 35 */
} /* 36 */
else if ($guess < $answer) { /* 37 */
echo "Too small" . "\n"; /* 38 */
$min = $guess; /* 39 */
} /* 40 */
else { /* 41 */
echo "You guess right" . "\n"; /* 42 */
$gameOver = true; /* 43 */
} /* 44 */
} /* 45 */
一開始先設置目標數字的下限 $min (第 4 行) 和上限 $max (第 5 行)。隨著遊戲進行,上下限會逐漸縮小,這是為了體貼玩這個程式的使用者。
使用 PHP 內建函式 rand 運算出答案 $answer (第 7 行)。使用隨機數的用意在讓每次的答案相異,增加遊戲的可玩性。
在遊戲迴圈開始前,設置遊戲狀態 $gameOver (第 9 行)。只要狀態不為真,遊戲會持續進行。
遊戲迴圈的前半部會從使用者取得 $guess (第 11 至 31 行)。使用 readline 函式從使用者取得資料,將其存在 $input 中 (第 15 至 17 行)。由於使用者的輸入尚未檢查是否正確與否,不宜直接存入 $guess。
附帶一提,readline 函式非 PHP 內建函式,需要安裝 php-readline 模組才能使用。
取得 $input 後,去除多餘的空白和換行 (第 19 行)。以常規表示式 (regular expression) 檢查 $input 是否像數字 (第 20 至 23 行)。雖然 $input 像數字,仍然是字串,將其轉型為整數 (第 25 行)。
經過一番工夫,確認 $input 是整數後,檢查 $input 是否符合上下限 (第 26 至 29 行)。確認符合後,將其存入 $guess (第 30 行)。
將 $guess 和 $answer 相比較,確認兩者是否相等 (第 33 至 44 行)。當 $guess 等於 $answer 時,代表遊戲結束。這時將遊戲狀態 $gameOver 設為 false (第 41 至 44 行)。反之,修改數字上下限,繼續下一輪遊戲。