Golang – 控制流程
迴圈(for、range、break、continue)
簡介
在任何程式語言中,迴圈都是實作重複性工作、遍歷資料集合、或是等待條件變化的核心工具。Go 語言把迴圈的語法設計得極為簡潔,只有單一關鍵字 for,卻能涵蓋傳統的 while、do‑while 以及 foreach 等所有情境。了解 for、range、break、continue 的使用方式,不僅能寫出可讀性高的程式碼,還能避免常見的效能與邏輯陷阱,讓你的 Go 程式在 可維護性 與 執行效率 上都有明顯提升。
本篇文章將從最基本的 for 語法說起,逐步帶入 range 的遍歷技巧、break/continue 的流程控制,以及實務上常見的使用情境與最佳實踐,適合剛踏入 Go 世界的初學者,也能為已有基礎的開發者提供系統性的回顧與進階觀點。
核心概念
1. 基本的 for 迴圈
Go 沒有 while 或 do‑while,所有迴圈都以 for 表示。最簡單的寫法如下:
for i := 0; i < 10; i++ {
fmt.Println(i)
}
- 初始化 (
i := 0) 只會在迴圈開始時執行一次。 - 條件判斷 (
i < 10) 每次迭代前都會評估,若為false則跳出迴圈。 - 後置語句 (
i++) 在每次迭代結束後執行。
變形:省略部份
// 只寫條件,相當於 while
for i < 5 {
fmt.Println(i)
i++
}
// 無條件迴圈,等同於 for(;;) → infinite loop
for {
// 必須在某個條件下使用 break 離開
}
2. range:遍歷切片、陣列、字典與通道
range 讓我們可以一次取得 索引(或鍵)與 值,語法如下:
// 切片
nums := []int{2, 4, 6, 8}
for idx, val := range nums {
fmt.Printf("第 %d 個元素 = %d\n", idx, val)
}
// 只要值
for _, val := range nums {
fmt.Println(val) // 忽略索引
}
// 字典
m := map[string]int{"apple": 3, "banana": 5}
for key, value := range m {
fmt.Printf("%s 有 %d 個\n", key, value)
}
// 通道 (channel)
ch := make(chan string, 3)
ch <- "golang"
ch <- "loop"
ch <- "range"
close(ch)
for msg := range ch {
fmt.Println("收到訊息:", msg)
}
- 切片/陣列:
range會回傳索引與對應的值。 - 字典:回傳鍵與值,遍歷順序是隨機的(不保證穩定)。
- 通道:
range會持續接收,直到通道被關閉。
小技巧:若只需要索引或值,使用底線
_佔位,可避免不必要的變數分配。
3. break 與 continue:細部控制迴圈流程
| 關鍵字 | 功能 | 常見用途 |
|---|---|---|
break |
立即跳出最近一層的迴圈 | 找到目標後停止搜尋 |
continue |
跳過本次迭代的剩餘程式,直接進入下一輪條件判斷 | 篩選不符合條件的資料 |
範例:使用 break 提前結束搜尋
func findFirstEven(nums []int) (int, bool) {
for i, v := range nums {
if v%2 == 0 {
return i, true // 找到即回傳
}
}
return -1, false // 沒有偶數
}
範例:使用 continue 跳過不需要的元素
func printOdd(nums []int) {
for _, v := range nums {
if v%2 == 0 {
continue // 偶數直接略過
}
fmt.Println(v) // 只會印出奇數
}
}
多層迴圈與標籤 (label):精準控制
outer:
for i := 0; i < 5; i++ {
for j := 0; j < 5; j++ {
if i*j > 6 {
fmt.Printf("break outer at i=%d, j=%d\n", i, j)
break outer // 跳出外層迴圈
}
}
}
注意:標籤只能用於
break或continue,且必須寫在同一個函式內,過度使用會降低程式可讀性,僅在確實需要「跳出多層迴圈」時才使用。
4. 迴圈的效能小提醒
避免在迴圈內重複建立大物件:如
make([]int, 0, 1000)放在迴圈外,或使用sync.Pool重新利用記憶體。range取得的切片值是****值的拷貝,若要修改原始元素,必須使用索引:for i := range nums { nums[i] *= 2 // 正確:直接改變原始 slice }字典遍歷不保證順序,若需要穩定順序,先把鍵收集起來並排序:
keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { fmt.Println(k, m[k]) }
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記 break/continue 的標籤 |
多層迴圈時直接寫 break 只會跳出內層,導致邏輯錯誤。 |
使用 label(如 outer:)或重新設計為單層迴圈 + 條件判斷。 |
range 迭代字典時的隨機順序 |
期望固定順序卻得到不一致結果。 | 先收集鍵並排序,再依序取值。 |
在 for 迴圈裡改變切片長度 |
可能導致索引越界或遺漏元素。 | 使用 for i := 0; i < len(s); i++ { … } 並在迴圈內適時更新 len(s),或使用 append 後重新取得長度。 |
在 for range 中取得指標 |
range 產生的值是臨時變數,取地址會得到同一個指標。 |
直接使用索引取得指標:for i := range slice { p := &slice[i] } |
過度使用 break/continue |
使程式流程斷斷續續,降低可讀性。 | 盡量以條件式包住需要執行的區塊,讓迴圈本身保持線性。 |
最佳實踐:
- 明確命名迭代變數:如
idx, val、key, value,避免使用單字母i、v以外的情況,提升可讀性。 - 盡量避免在迴圈內做 I/O(例如
fmt.Println),除非真的需要即時輸出,否則可先收集結果再一次性輸出,減少系統呼叫成本。 - 使用
go vet或staticcheck檢查迴圈中可能的錯誤(如未使用的索引、指標誤用)。 - 在需要大量計算的迴圈裡,考慮使用 goroutine + channel 進行平行化,但要注意競爭條件與同步成本。
實際應用場景
批次資料處理
讀取 CSV 檔案後,用for range逐行解析、過濾、轉換,最後一次性寫入資料庫。網路服務的請求佇列
使用緩衝通道chan Request,for req := range requestCh { handle(req) }持續處理進來的請求,break用於服務關閉時安全退出。搜尋與篩選
在大型切片中找出符合條件的第一筆資料,使用break立即停止迭代,降低不必要的遍歷成本。計算統計指標
例如計算一段時間內的最大、最小、平均值,常見寫法:var sum, max, min int for i, v := range data { sum += v if i == 0 || v > max { max = v } if i == 0 || v < min { min = v } } avg := float64(sum) / float64(len(data))遞迴轉迭代
將遞迴演算法(如樹的深度優先搜尋)改寫成for+stack,避免呼叫堆疊過深導致 panic。
總結
- Go 只提供 單一關鍵字
for,即可完成while、do‑while、foreach等所有迴圈需求。 range是遍歷 切片、陣列、字典、通道 的利器,配合索引/值或僅值的寫法,可大幅簡化程式碼。break與continue讓我們在迴圈內精確控制流程,必要時可使用 標籤 跳出多層迴圈。- 了解常見陷阱(如字典遍歷順序、切片指標取得方式)與最佳實踐(避免不必要的 I/O、適時使用
sync.Pool)能提升程式的可讀性與效能。 - 在實務開發中,迴圈是 資料處理、網路服務、演算法實作 的核心工具,熟練掌握後,寫出乾淨、快速且易於維護的 Go 程式將不再是難事。
把握這些概念,從今天起就能在 Go 專案中自信地使用迴圈,讓程式碼更簡潔、更高效!