所謂閉包是指內層函數引用了外層函數中的變量或稱為引用了自由變量的函數段誊,其返回值也是一個函數颤陶,了解過的語言中有閉包概念的像 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
的拷貝值傳進匿名函數之前蹄梢,只能獲取最后一次循環(huán)的值,這是新手最容易遇到的坑。
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() { //執(zhí)行所有匿名函數
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
操作僅將匿名函數放入到列表中禁炒,但并未執(zhí)行,并且引用的變量都是i
霍比,隨著i
的改變匿名函數中的i
也在改變幕袱,所以當執(zhí)行這些函數時,他們讀取的都是環(huán)境變量i
最后一次的值悠瞬。解決的方法就是每次復制變量i
然后傳到匿名函數中们豌,讓閉包的環(huán)境變量不相同。
若是你對閉包理解了浅妆,也可以利用閉包來修改全局變量:
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
調用會在當前函數執(zhí)行結束前才被執(zhí)行,這些調用被稱為延遲調用凌外,
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
總結:從形式上看,匿名函數都是閉包康辑。閉包的使用非常靈活摄欲,上面僅是幾個比較簡單的示例,不當的使用容易產生難以發(fā)現的 bug
疮薇,當出現意外情況時胸墙,首先檢查函數的參數,聲明可以接收參數的匿名函數惦辛,這些類型的閉包問題也就引刃而解了劳秋。