前言
預設情形下,電腦程式會逐行執行敘述。透過控制流程可以改變敘述執行的順序。本文介紹 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 行)。反之,修改數字上下限,繼續下一輪遊戲。