在 Go 語言中,閉包(closure)是一個函式值,它引用了其外部作用域中的變數。簡而言之,閉包能夠“捕獲”並“記住”其外部作用域中的變數,即使這個變數的生命週期已經結束。閉包的這種特性使得它在許多程式設計場景中非常有用,但也可能導致一些意外行為,尤其是在捕獲變數時。
捕獲問題的例子
一個常見的捕獲問題是迴圈變數的捕獲。下面是一個經典的例子:
package main
import "fmt"
func main() {
var funcs []func()
for i := 0; i < 3; i++ {
funcs = append(funcs, func() {
fmt.Println(i)
})
}
for _, f := range funcs {
f()
}
}
你可能期望這個程式輸出 0
, 1
, 2
,但實際上它會輸出 3
, 3
, 3
。原因在於閉包捕獲的是變數 i
的引用,而不是它的值。迴圈結束後,i
的最終值是 3
,因此所有的閉包都引用的是這個最終值。
解決方法
要解決這個問題,可以透過在每次迭代中建立一個新的變數來捕獲當前的值:
package main
import "fmt"
func main() {
var funcs []func()
for i := 0; i < 3; i++ {
i := i // 重新宣告一個新的 `i` 變數
funcs = append(funcs, func() {
fmt.Println(i)
})
}
for _, f := range funcs {
f()
}
}
或者使用一個輔助函式:
package main
import "fmt"
func main() {
var funcs []func()
for i := 0; i < 3; i++ {
funcs = append(funcs, createFunc(i))
}
for _, f := range funcs {
f()
}
}
func createFunc(i int) func() {
return func() {
fmt.Println(i)
}
}
在這兩個示例中,每個閉包都捕獲了不同的 i
值,這樣就能得到期望的輸出 0
, 1
, 2
。
總結
閉包捕獲變數時,容易出現捕獲變數引用而不是值的問題。特別是在迴圈中,這種問題更容易發生。透過在每次迭代中建立一個新的變數或者使用輔助函式,可以有效地避免這種問題。理解閉包的捕獲機制對於編寫正確且高效的 Go 程式碼非常重要。