Golang條件判斷語法全攻略

條件判斷是程式設計中不可或缺的控制結構,用於根據特定條件執行不同的程式碼路徑。Golang(Go語言)提供了簡潔且強大的條件判斷語法,本文將全面探討Go中的各種判斷寫法,從基本的if語句到更複雜的switch結構,以及一些獨特的Go語言實踐。

1. 基本if語句

Go的if語句與大多數程式語言相似,但有其特色——不需要括號包圍條件表達式。

1.1 標準if語句

1
2
3
if condition {
// 當condition為true時執行的程式碼
}

實際例子:

1
2
3
4
age := 18
if age >= 18 {
fmt.Println("成年人")
}

1.2 if-else語句

1
2
3
4
5
if condition {
// 當condition為true時執行
} else {
// 當condition為false時執行
}

實際例子:

1
2
3
4
5
6
temperature := 35
if temperature > 30 {
fmt.Println("天氣炎熱")
} else {
fmt.Println("天氣適宜")
}

1.3 if-else if-else鏈

1
2
3
4
5
6
7
if condition1 {
// 當condition1為true時執行
} else if condition2 {
// 當condition1為false且condition2為true時執行
} else {
// 當上述條件都不滿足時執行
}

實際例子:

1
2
3
4
5
6
7
8
9
10
11
12
score := 85
if score >= 90 {
fmt.Println("優")
} else if score >= 80 {
fmt.Println("良")
} else if score >= 70 {
fmt.Println("中")
} else if score >= 60 {
fmt.Println("及格")
} else {
fmt.Println("不及格")
}

1.4 帶有初始化語句的if

Go語言的一個特色是if語句可以包含一個初始化部分,該部分在條件檢查前執行。這樣宣告的變數只在if/else作用域內可見。

1
2
3
if initialization; condition {
// 程式碼
}

實際例子:

1
2
3
4
5
6
7
8
9
10
// 檢查檔案是否存在
if file, err := os.Open("file.txt"); err == nil {
// 檔案成功打開,可以使用file變數
defer file.Close()
// 處理檔案...
} else {
// 處理錯誤
fmt.Println("開啟檔案失敗:", err)
}
// 這裡不能訪問file或err變數,它們只在if/else區塊內有效

這種模式在處理可能返回錯誤的函數時非常有用,例如檔案操作、網路請求等。

2. switch語句

switch語句是一種多分支條件語句,用於根據表達式的值選擇不同的執行路徑。Go的switch比大多數語言更靈活。

2.1 基本switch語句

1
2
3
4
5
6
7
8
switch expression {
case value1:
// 當expression等於value1時執行
case value2, value3:
// 當expression等於value2或value3時執行
default:
// 當沒有匹配的case時執行
}

實際例子:

1
2
3
4
5
6
7
8
9
day := "星期三"
switch day {
case "星期一":
fmt.Println("開始新的一週")
case "星期六", "星期日":
fmt.Println("週末休息")
default:
fmt.Println("平常工作日")
}

2.2 不帶表達式的switch

Go允許switch語句不帶表達式,這時它會逐個檢查case條件,等價於if-else if-else鏈。

1
2
3
4
5
6
7
8
switch {
case condition1:
// 當condition1為true時執行
case condition2:
// 當condition2為true時執行
default:
// 當所有條件都為false時執行
}

實際例子:

1
2
3
4
5
6
7
8
9
hour := 15
switch {
case hour < 12:
fmt.Println("上午好")
case hour < 18:
fmt.Println("下午好")
default:
fmt.Println("晚上好")
}

2.3 帶有初始化語句的switch

與if類似,switch也可以包含初始化語句:

1
2
3
4
5
switch initialization; expression {
case value1:
// 程式碼
// ... 其他case
}

實際例子:

1
2
3
4
5
6
7
8
9
10
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("macOS系統")
case "linux":
fmt.Println("Linux系統")
case "windows":
fmt.Println("Windows系統")
default:
fmt.Printf("未知系統: %s\n", os)
}

2.4 fallthrough關鍵字

與其他語言不同,Go的switch語句預設情況下不會”落下”(fallthrough)到下一個case。但可以使用fallthrough關鍵字強制執行下一個case。

1
2
3
4
5
6
7
8
9
switch n := 4; n {
case 4:
fmt.Println("n等於4")
fallthrough
case 5:
fmt.Println("會執行這裡") // 即使n不等於5
case 6:
fmt.Println("不會執行這裡") // 因為上一個case沒有fallthrough
}

輸出:

1
2
n等於4
會執行這裡

2.5 Type Switch(型別判斷)

Type Switch是Go的一個獨特特性,用於根據變數的型別選擇不同的處理路徑。

1
2
3
4
5
6
7
8
switch x.(type) {
case type1:
// x是type1類型時執行
case type2:
// x是type2類型時執行
default:
// x是其他類型時執行
}

實際例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func printType(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("數值是整數: %d\n", v)
case string:
fmt.Printf("數值是字串: %s\n", v)
case bool:
fmt.Printf("數值是布林值: %v\n", v)
default:
fmt.Printf("未知型別: %T\n", v)
}
}

printType(42) // 數值是整數: 42
printType("hello") // 數值是字串: hello
printType(true) // 數值是布林值: true
printType(3.14) // 未知型別: float64

3. 特殊判斷結構與模式

3.1 使用select語句進行通道操作判斷

select語句是Go的一個獨特特性,專門用於同時等待多個通道操作,類似於switch但是專為通道設計。

1
2
3
4
5
6
7
8
9
10
select {
case <-channel1:
// 當channel1可讀時執行
case value := <-channel2:
// 當channel2可讀時執行,並將值賦給value
case channel3 <- value:
// 當可以向channel3發送數據時執行
default:
// 當所有通道都不可操作時立即執行
}

實際例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
func doWork(done <-chan bool, work <-chan int, result chan<- int) {
for {
select {
case <-done:
return // 收到退出信號,結束任務
case w := <-work:
// 接收到工作任務
result <- w * 2 // 處理工作並返回結果
case <-time.After(1 * time.Second):
fmt.Println("等待工作超時")
}
}
}

3.2 組合布林表達式

Go支援標準的布林運算符,可以組合多個條件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 邏輯與(AND):&&
if condition1 && condition2 {
// 當condition1和condition2都為true時執行
}

// 邏輯或(OR):||
if condition1 || condition2 {
// 當condition1或condition2任一為true時執行
}

// 邏輯非(NOT):!
if !condition {
// 當condition為false時執行
}

實際例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
age := 25
income := 30000
if age >= 18 && income >= 20000 {
fmt.Println("符合貸款條件")
}

raining := true
hasUmbrella := false
if raining && !hasUmbrella {
fmt.Println("會被淋濕")
}

weekend := false
holiday := true
if weekend || holiday {
fmt.Println("不用上班")
}

3.3 短路評估

Go的邏輯運算符使用短路評估(short-circuit evaluation):在&&運算中,如果第一個條件為false,則不會評估第二個條件;在||運算中,如果第一個條件為true,則不會評估第二個條件。

1
2
3
4
5
// 安全地訪問可能為nil的指針
if user != nil && user.IsActive {
// 只有在user非nil時才會檢查IsActive
fmt.Println("活躍用戶")
}

3.4 使用逗號ok慣用法進行判斷

Go中有一個常見的模式叫做”逗號ok”(comma ok)習慣用法,用於許多操作中進行安全檢查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 從映射(map)中安全獲取值
if value, ok := someMap[key]; ok {
// 鍵存在且ok為true時執行
fmt.Println("找到值:", value)
} else {
// 鍵不存在時執行
fmt.Println("鍵不存在")
}

// 型別斷言
if value, ok := someInterface.(Type); ok {
// 當接口值可以轉換為Type時執行
fmt.Println("轉換成功:", value)
} else {
// 轉換失敗時執行
fmt.Println("轉換失敗")
}

// 從通道接收值
if value, ok := <-someChan; ok {
// 當通道未關閉且成功接收時執行
fmt.Println("接收到值:", value)
} else {
// 當通道已關閉時執行
fmt.Println("通道已關閉")
}

4. 實用技巧與最佳實踐

4.1 使用自定義錯誤進行複雜判斷

可以通過定義自定義錯誤類型實現更精確的錯誤處理判斷:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 自定義錯誤類型
type ValidationError struct {
Field string
Msg string
}

func (e ValidationError) Error() string {
return fmt.Sprintf("驗證錯誤: %s - %s", e.Field, e.Msg)
}

// 使用自定義錯誤
func validateUser(user User) error {
if user.Name == "" {
return ValidationError{Field: "name", Msg: "不能為空"}
}
if user.Age < 0 {
return ValidationError{Field: "age", Msg: "不能為負數"}
}
return nil
}

// 判斷錯誤類型
func handleUser(user User) {
err := validateUser(user)
if err != nil {
switch e := err.(type) {
case ValidationError:
fmt.Printf("欄位 %s 不合法: %s\n", e.Field, e.Msg)
default:
fmt.Println("未知錯誤:", err)
}
return
}
fmt.Println("用戶合法")
}

4.2 使用defer與recover進行錯誤處理判斷

結合defer、panic和recover可以實現類似於其他語言中的try-catch機制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func safeOperation() (result int, err error) {
defer func() {
if r := recover(); r != nil {
fmt.Println("從panic恢復:", r)
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = fmt.Errorf("未知錯誤: %v", r)
}
}
}()

// 可能導致panic的操作
result = riskyFunction()
return result, nil
}

4.3 優雅處理多條件判斷

當有太多條件需要判斷時,使用map或函數可以避免過長的if-else鏈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 使用map替代switch
dayMessages := map[string]string{
"星期一": "開始新的一週",
"星期二": "繼續努力",
"星期三": "週中",
"星期四": "快到週末了",
"星期五": "準備迎接週末",
"星期六": "週末休息",
"星期日": "週末休息",
}

day := "星期三"
if message, exists := dayMessages[day]; exists {
fmt.Println(message)
} else {
fmt.Println("無效的日期")
}

// 使用函數映射
handlers := map[string]func(){
"create": handleCreate,
"update": handleUpdate,
"delete": handleDelete,
"view": handleView,
}

action := "update"
if handler, exists := handlers[action]; exists {
handler()
} else {
fmt.Println("未知操作")
}

4.4 使用泛型進行條件判斷(Go 1.18+)

從Go 1.18起,可以使用泛型使某些條件判斷更加通用:

1
2
3
4
5
6
7
8
9
10
11
12
// 定義泛型比較函數
func Max[T constraints.Ordered](a, b T) T {
if a > b {
return a
}
return b
}

// 使用
maxInt := Max(10, 20) // 20
maxFloat := Max(3.14, 2.71) // 3.14
maxString := Max("abc", "xyz") // "xyz"

5. 常見陷阱與注意事項

5.1 作用域問題

在if或switch的初始化語句中宣告的變數僅在條件區塊內有效:

1
2
3
4
5
6
if x := getValue(); x > 10 {
fmt.Println("x > 10:", x)
} else {
fmt.Println("x <= 10:", x)
}
// 這裡不能使用x

如果需要在區塊外使用變數,應該在外部宣告:

1
2
3
4
5
6
7
x := getValue()
if x > 10 {
fmt.Println("x > 10:", x)
} else {
fmt.Println("x <= 10:", x)
}
// 這裡可以使用x

5.2 nil的比較

在Go中,不同型別的nil值不能比較:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 正確做法 - 與特定型別的nil比較
var p *Person
if p == nil {
fmt.Println("p是nil")
}

// 陷阱 - 接口值的nil比較
var p *Person = nil
var i interface{} = p
if i == nil {
fmt.Println("i是nil") // 這行不會執行!
} else {
fmt.Println("i不是nil") // 這行會執行,因為i是非nil的接口值,包含一個nil的*Person
}

5.3 浮點數比較

直接比較浮點數相等可能導致意外結果,應該使用誤差範圍:

1
2
3
4
5
6
// 不可靠的比較
if a == b { /* ... */ } // 如果a和b是浮點數,這可能不可靠

// 更可靠的比較
const epsilon = 1e-9
if math.Abs(a-b) < epsilon { /* ... */ }

總結

Golang提供了多種條件判斷結構,從簡單的if-else到強大的switch和select語句,每種結構都有其適用場景。Go的條件判斷語法既保持了簡潔性,又提供了強大的表達能力。

理解並善用這些判斷寫法,可以讓你的Go程式碼更加清晰、優雅和高效。記住,在Go哲學中,清晰勝於聰明,簡單勝於複雜,這也適用於條件判斷的編寫。


參考資料:

  1. Go官方文檔:https://golang.org/doc/effective_go.html
  2. The Go Programming Language, Alan A. A. Donovan & Brian W. Kernighan