1.3 控制流程:if、for、switch
Go 控制流程的核心規則是:語法少但語意明確;if 處理條件分支,for 是唯一迴圈語法,switch 用於多分支判斷。本章將建立閱讀 Go 流程控制的基本模型。
if 表達條件與提前返回
if 的核心責任是根據條件決定程式是否進入某段邏輯。Go 的 if 條件不需要小括號,但區塊大括號是必要語法。
1if age >= 18 {
2 fmt.Println("adult")
3}Go 不會把數字、字串或指標自動當成布林值。條件必須是明確的 bool 表達式。
1count := 3
2
3if count > 0 {
4 fmt.Println("has items")
5}
6
7// if count {
8// fmt.Println("invalid")
9// }這個規則讓條件判斷更直接:讀者不需要猜某個非布林值在條件中會被如何轉換。
if 可以包含短宣告
if 的短宣告用來把只屬於這個判斷的暫存變數限制在區塊內。這讓錯誤處理與查找結果的作用範圍更清楚。
1if value, ok := cache["user:1"]; ok {
2 fmt.Println("cache hit:", value)
3}value 與 ok 只存在於 if 與對應的 else 區塊內。這種寫法適合處理 map 查找、型別轉換、函式呼叫錯誤等短生命週期資料。
1if err := saveProfile(profile); err != nil {
2 return err
3}這裡的 err 只用來判斷 saveProfile 是否失敗,離開 if 後就不再需要。短宣告可以降低變數留在外層範圍造成的干擾。
提前返回讓主流程靠左
Go 常用提前返回處理失敗或特殊情況。核心原則是先處理不能繼續的狀態,讓正常流程留在較少縮排的位置。
1func normalizeEmail(input string) (string, error) {
2 input = strings.TrimSpace(input)
3 if input == "" {
4 return "", fmt.Errorf("email is required")
5 }
6
7 if !strings.Contains(input, "@") {
8 return "", fmt.Errorf("invalid email")
9 }
10
11 return strings.ToLower(input), nil
12}這段程式先排除空字串與格式錯誤,最後才回傳正常結果。讀者可以依序看到「不能接受什麼」以及「通過檢查後會得到什麼」。
提前返回不是要求每個條件都拆開。當兩個條件代表同一個規則時,可以合併成一個判斷;當條件代表不同失敗原因時,拆開通常比較清楚。
for 是唯一迴圈語法
Go 的迴圈只有 for。它可以表達傳統計數迴圈、條件迴圈、無限迴圈與 range 迴圈。
1for i := 0; i < 3; i++ {
2 fmt.Println(i)
3}這是傳統的三段式迴圈:初始化、條件、迭代後處理。它適合需要 index、固定次數或精準控制遞增方式的場景。
1remaining := 3
2for remaining > 0 {
3 fmt.Println(remaining)
4 remaining--
5}省略初始化與後處理後,for 就是其他語言常見的 while 迴圈。Go 不另外提供 while,因為 for 條件 已經能表達同一件事。
1for {
2 fmt.Println("polling")
3 break
4}沒有條件的 for 是無限迴圈,通常會搭配 break、return、context 或 channel 退出。無限迴圈要讓退出條件清楚可見,否則很容易讓讀者無法判斷生命週期。
range 用來走訪集合
range 的核心用途是逐一走訪陣列、slice、map、字串與 channel。它會依資料型別產生不同的索引或值。
1names := []string{"alice", "bob"}
2
3for i, name := range names {
4 fmt.Println(i, name)
5}走訪 slice 時,第一個值是 index,第二個值是元素副本。若不需要 index,可以用 _ 忽略。
1for _, name := range names {
2 fmt.Println(name)
3}走訪 map 時,順序沒有保證。這是語言刻意設計的結果,避免程式誤以為 map 有穩定順序。
1scores := map[string]int{
2 "alice": 90,
3 "bob": 80,
4}
5
6for name, score := range scores {
7 fmt.Println(name, score)
8}如果輸出順序重要,應該先取出 key、排序,再依序讀取 map。
break 與 continue 控制迴圈節奏
break 的核心作用是結束目前迴圈,continue 的核心作用是跳過本次迭代並進入下一輪。它們應該用來表達清楚的流程轉折,而不是補救過度複雜的迴圈。
1for _, line := range lines {
2 if line == "" {
3 continue
4 }
5
6 if line == "STOP" {
7 break
8 }
9
10 fmt.Println(line)
11}這段程式忽略空行,遇到 STOP 停止,其他行則輸出。條件都放在處理邏輯前方,讀者可以先理解哪些資料不進入主流程。
當迴圈內的 break、continue、巢狀條件太多時,通常代表應該把部分邏輯抽成函式,讓每個函式只負責一層判斷。
switch 表達多分支判斷
switch 的核心責任是把同一個概念的多種可能集中呈現。Go 的 switch 預設不會自動落入下一個 case,所以大多數情況不需要寫 break。
1switch method {
2case "GET":
3 fmt.Println("read")
4case "POST":
5 fmt.Println("create")
6case "DELETE":
7 fmt.Println("delete")
8default:
9 fmt.Println("unsupported")
10}每個 case 預設只執行自己的區塊。若真的需要落入下一個 case,Go 提供 fallthrough,但日常程式很少需要它。
switch 也可以不帶目標值,用來取代一長串 if else。
1switch {
2case score >= 90:
3 fmt.Println("A")
4case score >= 80:
5 fmt.Println("B")
6case score >= 70:
7 fmt.Println("C")
8default:
9 fmt.Println("D")
10}這種寫法適合條件都在描述同一個分類規則時使用。若每個條件都在處理不同概念,拆成多個 if 或不同函式通常更清楚。
下一章
下一章會回到 package 與檔案組織,說明 Go 如何用 package 建立程式邊界。