Golang 中關於閉包的坑


所謂閉包是指內層函數引用了外層函數中的變量或稱為引用了自由變量的函數,其返回值也是一個函數,了解過的語言中有閉包概念的像 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
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM