前言
在本系列文章先前的範例程式中,大部分程式都是由上往下依序執行。在程式中使用控制結構 (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
敘述。藉由這些選擇控制結構,我們可以在程式中實作可調整的行為,讓程式有判斷能力。