所謂閉包是指內層函數引用了外層函數中的變量或稱為引用了自由變量的函數,其返回值也是一個函數,了解過的語言中有閉包概念的像 js,python,golang
都類似這樣。
python
中的閉包可以嵌套函數,像下面這樣:
def make_adder(addend): def adder(augend): return augend + addend return adder
轉化成 golang
代碼則像下面這樣:
func outer(x int) func(int) int{ func inner(y int) int{ return x + y } return inner }
當然這是錯誤的,golang
中是不能嵌套函數的,但是可以在一個函數中包含匿名函數,完整示例像下面這樣:
package main import ( "fmt" ) func outer(x int) func(int) int { return func(y int) int { return x + y } } func main() { f := outer(10) fmt.Println(f(100)) }
看了一段時間 golang
后,對於 golang
中的閉包可能出現的坑大致有下面幾個。
1,for range 中使用閉包
一個示例:
func main() { s := []string{"a", "b", "c"} for _, v := range s { go func() { fmt.Println(v) }() } select {} // 阻塞模式 } // 嗯,結果應該是 a,b,c 吧
來看看結果:

輸出的結果不期而然,大家的結果也不一定和我相同。
對比下面的改進:
func main() { s := []string{"a", "b", "c"} for _, v := range s { go func(v string) { fmt.Println(v) }(v) //每次將變量 v 的拷貝傳進函數 } select {} }
所以結果當然是:
"a" "b" "c"
由於使用了 go
協程,並非順序輸出。
解釋:也不用多解釋了吧,在沒有將變量
v
的拷貝值傳進匿名函數之前,只能獲取最后一次循環的值,這是新手最容易遇到的坑。
2,函數列表使用不當
package main import ( "fmt" ) func test() []func() { var s []func() for i := 0; i < 3; i++ { s = append(s, func() { //將多個匿名函數添加到列表 fmt.Println(&i, i) }) } return s //返回匿名函數列表 } func main() { for _, f := range test() { //執行所有匿名函數 f() } }
運行結果:

解決方法:
package main import ( "fmt" ) func test() []func() { var s []func() for i := 0; i < 3; i++ { x := i //復制變量 s = append(s, func() { fmt.Println(&x, x) }) } return s } func main() { for _, f := range test() { f() } }
解釋:每次
append
操作僅將匿名函數放入到列表中,但並未執行,並且引用的變量都是i
,隨着i
的改變匿名函數中的i
也在改變,所以當執行這些函數時,他們讀取的都是環境變量i
最后一次的值。解決的方法就是每次復制變量i
然后傳到匿名函數中,讓閉包的環境變量不相同。
若是你對閉包理解了,也可以利用閉包來修改全局變量:
package main import ( "fmt" ) var x int = 1 func main() { y := func() int { x += 1 return x }() fmt.Println("main:", x, y) } // 結果: main: 2 2
3,延遲調用
defer
調用會在當前函數執行結束前才被執行,這些調用被稱為延遲調用,
defer
中使用匿名函數依然是一個閉包。
package main import "fmt" func main() { x, y := 1, 2 defer func(a int) { fmt.Printf("x:%d,y:%d\n", a, y) // y 為閉包引用 }(x) // 復制 x 的值 x += 100 y += 100 fmt.Println(x, y) }
輸出結果:
101 102
x:1,y:102
總結:從形式上看,匿名函數都是閉包。閉包的使用非常靈活,上面僅是幾個比較簡單的示例,不當的使用容易產生難以發現的 bug
,當出現意外情況時,首先檢查函數的參數,聲明可以接收參數的匿名函數,這些類型的閉包問題也就引刃而解了。
作者:田飛雨
鏈接:https://www.jianshu.com/p/fa21e6fada70
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。