位元詩人 [PHP] 程式設計教學:控制結構 (Control Structure)

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

預設情形下,電腦程式會逐行執行敘述。透過控制流程可以改變敘述執行的順序。本文介紹 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 是介於 110 之間的正整數,該數使用 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 的值執行不同的指令。

valuea 時,執行 a 區塊內的 statement。在 a 區塊的敘述走完時,會碰到 break 敘述,這時會跳開整個 switch 敘述。

valueb 時,沒有碰到 break 敘述,故會繼續往 c 區塊走。在 c 區塊完成時,會碰到 break 敘述,跳離 switch 敘述。這樣的行為稱為 fallthrough 。用得好是特性,用不好變臭蟲。

valuec 時,行為類似 a,不重述。

value 不屬於 abc 任一值時,會執行 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 敘述的條件不符合,即跳離此迴圈。實際的效果為逐一印出 110 的數字。

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 敘述,提早離開此迴圈。實際的行為是從 15 印出數字五次。

continue 敘述

使用 continue 敘述可以略過該輪迭代,繼續下一輪迭代。以下是實際的程式:

<?php

for ($i = 1; $i <= 10; ++$i) {
    if (0 == $i % 2) {
        continue;
    }

    echo $i, "\n";
}

這個範例類似於先前的 for 敘述。但 $i 為偶數 (對 2 取餘數為 0) 時,會觸發 continue 敘述,略去該輪迴圈剩下的敘述。實際的行為是印出 13579 五個奇數。

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 行)。反之,修改數字上下限,繼續下一輪遊戲。

關於作者

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

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