什么是闭包?
一个持有外部环境变量的函数就是闭包(可以描述为引用了自由变量 的函数或者描述为闭包是指内层函数引用了外层函数中的变量) 。理解闭包通常有以下几个关键点:
- 函数
- 自由变量
- 环境
举个例子:
func a(x, y int) { //函数a的作用域则是环境
fmt.Println(x, y) //此处x,y均不是自由变量
func b() {
fmt.println(x, y) //在内部函数b中,x,y相对于b是自由变量
}()
//无论b最终是否会作为返回值被函数a返回,b本身都已经形成了闭包
}
解释: 函数b因为捕获了外部作用域(环境)中的变量x,y,因此形成了闭包。而变量x, y并不属于函数b,所以在概念里被称为「自由变量」。
golang闭包的坑
浅坑
浏览下面的代码,然后写出返回的结果:
package main
import (
"fmt"
)
func outer(x int) func(int) int {
return func(y int) int {
return x + y
}
}
func main() {
f, x := outer(10)
fmt.Println(f(100))
}
返回结果为:110(很好理解,不做解释,不懂的继续往下看即可理解)
1 package main
2
3 import (
4 "fmt"
5 )
6
7 func outer(x int) (func(int) int, *int) {
8 return func(y int) int {
9 return x + y
10 }, &x
11 }
12
13 func main() {
14 f, x := outer(10)
15 fmt.Printf("x's value: %d, %p\n", *x, x)
16 *x = 20
17 fmt.Println(f(100))
18 }
返回结果为:
x's value: 10, 0xc42000e218
120
此处就会产生疑问,outer函数内赋值的变量x,为什么在main函数中能够修改?
答:使用逃逸分析就可以很清晰的解释这个问题。首先,go在一定程度上消除了堆和栈的区别,因为go 在编译的时候进行逃逸分析来决定一个对象放栈上还是堆上,不逃逸的对象放栈上,可能逃逸的放堆上 。其次,go开启逃逸分析分析有两种选择,一是可以在编译的时候使用go build --gcflags=-m main.go
,二是在运行的时候使用
go run -gcflags '-m -l' main.go
(-l 是为了取消自动内联)。最后,看下上述代码的逃逸分析:
分析结果:
➜ go run -gcflags '-m -l' test.go
# command-line-arguments
./test.go:8: func literal escapes to heap
./test.go:8: func literal escapes to heap
./test.go:9: &x escapes to heap
./test.go:7: moved to heap: x
./test.go:10: &x escapes to heap
./test.go:15: *x escapes to heap
./test.go:15: x escapes to heap
./test.go:17: f(100) escapes to heap
./test.go:15: main ... argument does not escape
./test.go:17: main ... argument does not escape
x's value: 10, 0xc42007e018
120
结论 :通过逃逸分析发现,变量x逃逸到堆上,所有在main函数中能够访问并修改。所以最终打印的值为120。
深坑
当defer遇到闭包就是深坑,defer调用会在当前函数执行结束前才被执行,defer中使用匿名函数依然是一个闭包。
package main
import "fmt"
func main() {
x, y := 1, 2
defer func(a int) {
fmt.Printf("a:%d,y:%d\n", a, y) // y 为闭包引用
}(x) // 复制 x 的值
x += 100
y += 100
fmt.Println(x, y)
}
输出结果:
101 102
x:1,y:102
原因: main函数会在defer匿名函数打印之前打印101 102,这一点无需解释;defer匿名函数中a的打印值为1,是因为函数在定义的时候就拷贝了x变量给a,y的打印值为102是因为闭包引用了外部main函数自由变量l的原因。
参考资料
版权声明:本文为phantom_111原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。