闭包是什么?
闭包是在词法上下文中引用了自由变量的函数,这种说法可能太过学术化了,很难理解。
用通俗的话来说, 闭包相当于在一个函数中,去捕获自由变量(在函数外部定义但在函数内被引用的变量) 。当脱离了捕获该自由变量的上下文,依旧可以使用该自由变量。
我们来看一个例子
func test(x int) func() {
return func() {
fmt.Println(x)
}
}
func main() {
f := test(1)
f()
}
输出结果为 1
test返回的匿名函数会引用上下文环境变量 x, 当该函数在 main 执行的时候, 它依然可以正确读取 x 的值, 这种现象就称之为闭包
那么闭包是如何实现呢?
首先我们可以想到这个变量应该不会是在栈中进行分配的,而是在堆中进行分配。如果该变量在栈中进行分配的话,函数返回之后,该变量应该会被回收,所以只可能是在堆中进行分配。
在继续探讨 闭包的实现问题之前,我们可以先去了解一下 escape analyze 机制
escape analyze(逃逸分析机制) 是一种确定指针动态范围的方法-它可以分析程序在哪些地方可以访问到指针,它涉及指针分析和形状分析。
举个例子
func f() *Cursor {
var c Cursor
c.X = 500
noinline()
return &c
}
这种返回局部变量的办法在C语言上是不允许的,但是在Go语言中是明确规定为合法的,语言会自动识别这种情况并在堆上分配相应的内存,而不是在函数栈上分配内存。
我们可以通过
go build --gcflags=-m main.go
我们可以看到输出
./main.go:20: moved to heap: c .
/main.go:23: &c escapes to heap 表示c逃逸了,被移到堆中。
我们重新回到闭包的实现来看,闭包既然是函数和它引用的环境组成,那是不是我们可以通过一个类去进行表示闭包呢?(如C++中的lambda表达式 一样,通过一个匿名类来标志闭包)
我们可以将上述来这样理解
type Closure struct {
F func() int
i *int
}
闭包有什么意义呢?
缩小变量作用域,减少对全局变量的污染,并且闭包可以让我们不用传递参数就可以读取或者修改环境状态。
闭包可以作为一种有自身状态的函数
package main
import (
"fmt"
)
func adder() func() int {
sum := 0
return func() int {
sum ++
return sum
}
}
func main() {
myAdder := adder()
// 从1加到10
for i := 1; i <= 10; i++ {
myAdder()
}
fmt.Println(myAdder())
fmt.Println(myAdder())
}
输出
11
12
小结
-
Go语言能通过escape analyze识别出变量的作用域,自动将变量在堆上分配。将闭包环境变量在堆上分配是Go实现闭包的基础。
-
返回闭包时并不是单纯返回一个函数,而是返回了一个结构体,记录下函数返回地址和引用的环境中的变量地址。
-
闭包可以缩小变量作用域,减少对全局变量的污染,可以让我们不用传递参数就可以读取或者修改环境状态