Michelle Chen [Java] 程式設計教學:使用控制結構 (Control Structure)

Java控制結構
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

電腦程式預設的執行順序是由上而下,逐條敘述執行。透過控制結構,可以改變程式執行的順序。本文介紹 Java 的控制結構。

if 敘述

if 敘述是基本的選擇控制結構 (selection control structure)。最簡單的 if 敘述為一元結構。只有在 condition 為真時才會執行區塊內的程式碼:

if (condition) {
    /* Run code if `condition` is true. */
}

除了一元敘述外,可以加上選擇性的 else 敘述,形成二元選擇結構。當 condition 為真時,執行 if 敘述部分的程式碼;反之,則執行 else 敘述內的程式碼:

if (condition) {
    /* Run code if `condition` is true. */
}
else {
    /* Run code if `condition` is not true. */
}

承上,還可以再加上選擇性的 else if 敘述,形成多元選擇結構。範例虛擬碼如下:

if (condition_a) {
    /* Run code if `condition_a` is true. */
}
else if (condition_b) {
    /* Run code if `condition_b` is true. */
}
else {
    /* Run code if all conditions are false. */
}

由於 else if 敘述和 else 敘述都是選擇性的,以下虛擬碼也是合理的 Java 控制結構:

if (condition_a) {
    /* Run code if `condition_a` is true. */
}
else if (condition_b) {
    /* Run code if `condition_b` is true. */
}

看完虛擬碼後,來看一下實際範例。此範例以 java.util.Random 物件建立隨機整數,然後後 if 敘述判斷該隨機數是奇數還是偶數:

import java.util.Random;

public class MainProgram
{
    public static void main (String[] args)
    {
        var max = 10;
        var min = 1;

        var random = new Random();
        var n = random.nextInt(max - min + 1) + min;

        if (n % 2 == 0) {
            System.out.println(n + " is even");
        }
        else {
            System.out.println(n + " is odd");
        }
    }
}

(選擇性) if 敘述的排版方式

大部分 C 家族語言使用以下方式來排版:

if (a) {
    /* Some code here. */
} else if (b) {
    /* Some code here. */
} else {
    /* Some code here. */
}

Golang 甚至把這種風格強制寫進編譯器裡,沒得調整。

但筆者在寫 C 家族語言的 if 敘述時,會採用以下方式:

if (a) {
    /* Some code here. */
}
else if (b) {
    /* Some code here. */
}
else {
    /* Some code here. */
}

這是因為 if 敘述、else if 敘述、else 敘述在概念上屬於同一層。寫巢狀結構時,這樣的排版方式會比傳統的方式更加清楚。

或者是參考 C# (C sharp) 的排版方式:

if (a)
{
    /* Some code here. */
}
else if (b)
{
    /* Some code here. */
}
else
{
    /* Some code here. */
}

這種方式也和筆者的排版方式有相同效果。C# 會採用這種排版方式是因為 C# 承襲 Pascal 的慣例。

switch 敘述

switch 敘述是另一種選擇控制結構。這種控制結構僅限於值比較。以下是 switch 的虛擬碼:

switch (value) {
case a:
    /* Run code if `a`. */
    break;
case b:
    /* Run code if `b`. */
    /* Fallthrough. */
case c:
    /* Run code if `c`. */
    break;
default:
    /* Run code if all cases are false. */
    break;
}

switch 的雷是忘了在 case 敘述的末端加上 break 敘述,就會穿過該 case 敘述,到達下一個 case 敘述。這樣的特性稱為 fallthrough。有時候 fallthough 是程式設計者刻意安排的,但常常是 bug 的來源。

看完虛擬碼後,來看一下實際範例。此範例程式求得今日是星期幾 (day of week),然後根據該值印出相對應的評語:

import java.util.Date;
import java.util.GregorianCalendar;

public class MainProgram
{
    public static void main (String[] args)
    {
        var now = new Date();
        var c = GregorianCalendar.getInstance();
        c.setTime(now);
        var dow = c.get(GregorianCalendar.DAY_OF_WEEK);

        switch (dow) {
            case 6:
            case 7:
                System.out.println("Weekend");
                break;
            case 5:
                System.out.println("Thanks God. It's Friday");
                break;
            case 3:
                System.out.println("Hump day");
                break;
            default:
                System.out.println("Week");
                break;
        }
    }
}

注意這裡用到一次 fallthrough,這不是 bug,而是符合該程式預期行為的安排。

while 敘述

while 敘述是基本的迭代控制結構。只要 condition 符合條件,while 迴圈會持續執行同一段程式碼區塊。以下是 while 的虛擬碼:

while (condition) {
    /* Run code while `condition` is true. */
}

看過虛擬碼後,來看實際的程式碼:

var i = 1;
while (i <= 10) {
    System.out.println(i);
    ++i;
}

由於 while 迴圈會持續執行同一段程式碼,若結束條件寫錯,會變成無窮迴圈 (infinite loop)。有時候無窮迴圈是程式設計者刻意設計的,但時常是 bug 的來源。

for 敘述

使用計數器 (Counter)

傳統的 for 迴圈使用計數器來控制執行次數。使用計數器的 for 迴圈虛擬碼如下:

for (start; end; step) {
    /* Run code if not `end`. */
}

看過虛擬碼後,來看實際範例:

for (var i = 1; i <= 10; ++i) {
    System.out.println(i);
}

使用迭代器 (Iterator)

使用迭代器的 for 迴圈需要搭配容器 (collection) 或迭代器,故留到介紹容器時才一併說明。

改變迴圈行進的敘述

break 敘述

break 敘述可提早中斷迴圈。參考以下例子:

for (var i = 1; i <= 10; ++i) {
    if (i > 5)
        break;

    System.out.println(i);
}

先前提到的 switch 敘述也是用 break 敘述來區隔 case 敘述。

continue 敘述

continue 敘述用來跳過該次迭代剩下的程式碼。可參考以下例子:

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

    System.out.println(i);
}

(選讀) 終極密碼 (Da Vinci Code)

先前的範例偏短,較難體會使用控制結構寫程式的感覺。本節使用一個稍長的範例來看如何使用 Java 控制結構。這個範例程式是一個常見的桌遊。

此程式的執行過程如下:

$ java MainProgram
Input a number (1, 100): 50
Too small
Input a number (50, 100): 75
Too big
Input a number (50, 75): 63
Too big
Input a number (50, 63): 57
Too big
Input a number (50, 57): 53
You guess right

小伙伴可以試著先不要看範例程式碼,自己實作看看。以下是參考範例,我們會在後文說明此範例:

import java.util.Random;                                /*  1 */

public class MainProgram                                /*  2 */
{                                                       /*  3 */
    public static void main (String[] args)             /*  4 */
    {                                                   /*  5 */
        /* Set the initial range of a answer. */        /*  6 */
        var max = 100;                                  /*  7 */
        var min = 1;                                    /*  8 */

        /* Get a random answer
            between `min` and `max`. */
        var random = new Random();                      /*  9 */
        var answer =                                    /* 10 */
            random.nextInt(max - min + 1) + min;        /* 11 */

        while (true) {                                  /* 12 */
            /* Try to receive a valid input
                from the user. */
            int guess;                                  /* 13 */
            while (true) {                              /* 14 */
                /* Prompt the user for an input. */     /* 15 */
                var input = System                      /* 16 */
                    .console()                          /* 17 */
                    .readLine(                          /* 18 */
                        "Input a number "               /* 19 */
                        + "(" + min + ", "              /* 20 */
                              + max + "): ");           /* 21 */

                try {                                   /* 22 */
                    /* Parse the input. */              /* 23 */
                    guess = Integer.parseInt(input);    /* 24 */
                }                                       /* 25 */
                catch (Exception e) {                   /* 26 */
                    /* It fails to parse the input.
                        Skip to next iteration. */

                    System.out                          /* 27 */
                        .println(                       /* 28 */
                            "Invalid number: "          /* 29 */
                            + input);                   /* 30 */
                    continue;                           /* 31 */
                }                                       /* 32 */

                /* The guess out of range.
                    Skip to next iteration. */
                if (!(min <= guess && guess <= max)) {  /* 33 */
                    System.out                          /* 34 */
                        .println(                       /* 35 */
                            "Number out of range: "     /* 36 */
                            + guess);                   /* 37 */
                    continue;                           /* 38 */
                }                                       /* 39 */

                /* The guess is valid.
                    Exit the input loop. */
                break;                                  /* 40 */
            }                                           /* 41 */

            if (guess < answer) {                       /* 42 */
                System.out.println("Too small");        /* 43 */
                min = guess;                            /* 44 */
            }                                           /* 45 */
            else if (answer < guess) {                  /* 46 */
                System.out.println("Too big");          /* 47 */
                max = guess;                            /* 48 */
            }                                           /* 49 */
            else {                                      /* 50 */
                System.out.println("You guess right");  /* 51 */
                /* The guess is equal to the answer.
                    Exit the game loop. */
                break;                                  /* 52 */
            }                                           /* 53 */
        }                                               /* 54 */
    }                                                   /* 55 */
}                                                       /* 56 */

為了簡化範例,此程式的上下限是固定的 (第 7、8 行)。根據此上下限求出一隨機答案 (第 9 至 11 行)。

大部分的程式碼是一個遊戲迴圈 (game loop) (第 12 至 54 行)。該迴圈可分為取得使用者輸入 (第 13 至 41 行) 和判讀答案 (第 42 至 53 行) 兩部分。

程式無法預知使用者是否輸入合理的數字,所以再用一個無窮迴圈,直到使用者輸入合理的數字才跳出此迴圈 (第 40 行)。使用 readLine() 函式取得使用者輸入 (第 16 至 21 行)。但使用者可能輸入錯誤的數字,故使用 parseInt() 函式試著解析使用者輸入,若使用者輸入錯誤的輸入,則略過剩下的程式碼 (第 22 至 32 行)。即使使用者輸入合理的數字,也可能超出上下限,所以要進行檢查 (第 33 至 39 行)。

確認使用者輸入合理的數字後,判讀使用者是否猜對 (第 42 至 53 行)。若使用者猜對,則中止遊戲迴圈 (第 50 至 53 行)。反之,則更新程式的上下限 (第 42 至 49 行)。

若讀者想繼續練習,以下是幾個加強此程式的方向:

  • 由使用者決定答案的上下限
  • 限定猜測次數。超過猜測次數算失敗
  • 給予使用者起始分數,猜對加分,猜錯扣分。當分數歸零時結束遊戲,當分數超過閥值時判定使用者獲勝

這些部分就留給小伙伴自行嘗試,這裡不再展示範例程式碼。