前言
在本系列文章先前的範例程式中,大部分程式都是由上往下依序執行。在程式中使用控制結構 (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_a 及 cond_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_a 及 cond_b 皆不為真時,兩區塊內的程式碼皆不會執行。
我們已經觀看了不少 Go 虛擬碼,現在來看一個實際的例子。我們建立一個介於 1 至 10 的整數,並判斷其為奇數 (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 等於 d 或 e 時,皆會執行 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 敘述將本範例改寫成等效的程式,就可以體會兩者的差別。
結語
在本文中,我們介紹了 if 和 switch 敘述。藉由這些選擇控制結構,我們可以在程式中實作可調整的行為,讓程式有判斷能力。