美思 [Golang] 程式設計教學:使用選擇控制結構 (Selection Control Structure)

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

前言

在本系列文章先前的範例程式中,大部分程式都是由上往下依序執行。在程式中使用控制結構 (control structure) 可以改變程式運行的方向,藉以達成我們所設計的行為。在本文中,我們介紹 Go 語言中和選擇 (selection)、分支 (branching) 相關的控制結構。

if

if 是大部分程式語言中基本的選擇結構,用於選擇性執行某段程式碼。在 Go 語言中,最簡單的 if 敘述只有單一區塊 (block)。以 Go 虛擬碼撰寫如下:

if cond {
    // Run code here.
}

cond 為真時,才執行該區塊內的程式碼。

除了單一區塊,我們還可以加上一個選擇性的 else 區塊。如下例:

if cond {
    // Run code here if `cond` is true.
} else {
    // Run code here if `cond` is not true.
}

在這段 Go 虛擬碼中,當 cond 為真時,執行第一段區塊內的程式碼;反之,執行 else 區塊內的程式碼。透過 if 區塊和 else 區塊的結合,if 敘述變成二元敘述。

除此之外,我們還可以透過選擇性的 else if 敘述形成多元敘述。如下例:

if cond_a {
    // Run code here if `cond_a` is true.
} else if cond_b {
    // Run code here if `cond_b` is true.
} else {
    // Run code here if neither `cond_a` or `cond_b` is true.
}

在這段 Go 虛擬碼中,當 cond_a 為真時,執行第一段區塊內的程式碼。當 cond_a 為偽而 cond_b 為真時,執行第二段區塊內的程式碼。當 cond_acond_b 皆不為真時,則執行 else 區塊內的程式碼。

由於 else 敘述是選擇性的,若不需要可去除。如下例:

if cond_a {
    // Run code here if `cond_a` is true.
} else if cond_b {
    // Run code here if `cond_b` is true.
}

在這個例子中,當 cond_acond_b 皆不為真時,兩區塊內的程式碼皆不會執行。

我們已經觀看了不少 Go 虛擬碼,現在來看一個實際的例子。我們建立一個介於 110 的整數,並判斷其為奇數 (odd) 或偶數 (even):

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    // Create a random seed by system time.
    s := rand.NewSource(time.Now().UnixNano())

    // Create a random object.
    r := rand.New(s)

    // Create a random integer by `r`.
    n := r.Intn(10) + 1

    if n%2 == 0 {
        fmt.Printf("%d is even\n", n)
    } else {
        fmt.Printf("%d is odd\n", n)
    }
}

在這裡,我們第一次看到建立隨機數 (random number) 的方法,所以我們稍微岔題一下,講一下隨機數生成。

在電腦程式中,我們用擬隨機數生成 (pseudo-random number generation) 的演算法 (algorithm) 來生成隨機數。其實擬隨機數並不是隨機生成的,只是生成的數字看起來沒有規律。我們這裡不詳談擬隨機數生成的演算法,只介紹其使用方式:

  • 先餵給隨機函式一個起始數字,做為種子 (seed)
  • 隨機數生成函式根據種子產生隨機數並回傳。該隨機數同時做為下一輪的種子
  • 若需要更多隨機數,則重覆前述步驟即可

若我們給定的種子是固定的數,每次產生的擬隨機數數列會相同。為了要讓初始數字是非固定數,使用當下的系統時間是常見的方式。本例也以系統時間做為種子。

在本例中,s 是種子物件,而 r 是根據 s 所產生的隨機物件。之後以 r 生成隨機數。Intn 函式會產生從 0 (包含) 至 n (排除) 隨機整數。

本例的 if 敘述相當簡單,以 n % 2 == 0 為條件。當該條件為零時,在終端機印出 n 是偶數的訊息;反之,在終端機印出 n 是奇數的訊息。

switch

switch 敘述算是一種語法糖,用意在簡化 if 敘述的語法。雖然 Go 語言抱持極簡主義,因 switch 是 C 家族語言的傳統,Go 語言保留了這項特性,並做出一些細微的改良。

我們來看一個稍微長一點的 Go 虛擬碼:

switch v {
case a:
    // Run block_a here.
case b:
    // Run block_b here.
    fallthrough
case c:
    // Run block_c here.
case d, e:
    // Run block_d_e here.
default:
    // Run default block.
}

在這個例子中,switch 會檢查資料 v,並根據 v 的值導引到不同的分支上。

v 等於 a 時,會執行 block_a 內的程式碼,然後離開 switch 敘述。

v 等於 b 時,會先執行 block_b 內的程式碼,再執行 block_c 內的程式碼,然後離開 switch 敘述。為什麼會執行兩段程式碼呢?因為 block_b 的尾端加上 fallthrough 敘述。這時候 Go 程式會繼續執行下一段程式碼。而 block_c 的尾端沒有 fallthrough,就會停下來。

原本 C 語言的 switch 敘述是不加 break 敘述時,就自動 fallthrough。這項特性很容易造成 bug。在 Go 語言就加上防呆機制,降低潛在的 bug。

v 等於 c 時,會執行 block_c 區塊內的程式碼,但不會回頭執行 block_b 的程式碼。

v 等於 de 時,皆會執行 block_d_e 內的程式碼。Go 的 switch 敘述允許兩個以上的值,比原本的 C 版本的 switch 還容易撰寫。

當所有條件都不符合時,就執行 default 區塊內的程式碼。default 類似於 else,同樣是選擇性的,若不需要可省略。

我們來看一個實際的範例。在這個例子中,我們根攄系統當下的日期 (day of week) 印出相對應的字串。

package main

import (
    "fmt"
    "time"
)

func main() {
    // Get current system time.
    now := time.Now()

    // Extract current day of week from `now` object.
    weekOfDay := now.Weekday()

    switch weekOfDay {
    case 6, 7:
        fmt.Println("Weekend")
    case 5:
        fmt.Println("Thank God. It's Friday")
    case 3:
        fmt.Println("Hump day")
    default:
        fmt.Println("Week")
    }
}

一開始,我們使用 time 套件的 Now 函式取得當下的系統時間。

接著,我們使用 Weekday 函式取出當下的日期 (day of week)。日期是 Weekday 型別,而 Weekday 型別是整數的別名,故 weekOfDay 可用整數做為檢查的條件。

在本範例的 switch 敘述中,我們有四種情境。由於程式碼相對簡單,讀者可試著自行閱讀。

讀者可試著用 if 敘述將本範例改寫成等效的程式,就可以體會兩者的差別。

結語

在本文中,我們介紹了 ifswitch 敘述。藉由這些選擇控制結構,我們可以在程式中實作可調整的行為,讓程式有判斷能力。

關於作者

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

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