闭包
1. 什么是闭包
在一个外函数中定义一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。通俗的讲就是在函数内部引用了函数内部的变量的函数
案例
Go语言中通过匿名函数实现闭包的
func adder()func()int{
c=2
return func()int{
return c
}
}
func main(){
f1 := add()
f2 := add()
fmt.Println(f1())
fmt.Println(f2())
}
运行结果为2,2
这里的f1
和f2
就称为两个Function value
,Function value
本质上是一个指针,但是并不是直接指向函数入口,而是指向一个runtime.funcval结构体,这个结构体中只有一个地址fn
指向函数指令的入口
来看下面示图:
main
函数栈帧有两个局部变量f1
,f2
,然后是返回值空间,add
函数中有一个局部变量c = 2
,执行时因为f1 := add()
,add
函数会在堆上分配一个funcval
结构体,其中fn
指向代码段中的闭包函数入口,同时还有一个捕获列表,这里只捕获了变量c,通过f1()
调用函数时,这个结构体的起始地址addr2
,就被作为返回值写入返回值空间,返回值空间中地址为addr2
,f1
就被赋值为addr2
,如果再次调用add函数时就会再创建一个funcval结构体
如何获取捕获列表中变量c呢?
go语言中通过funcion value调用函数时,会将funcval存放到寄存器中,通过寄存器取出funcval结构体的地址,再加上相应的偏移量就找到捕获列表的地址
再看一个案例
func create((fs [2]func()){
for i=0;i<2;i++{
fs[i]=func(){
fmt.Println(i)
}
}
return
}
func main(){
fs := create()
for i :=0; i<len(fs); i++{
fs[i]()
}
}//2,2
由于被闭包捕获,i
变量发生了内存逃逸,逃逸到了堆上,所以在栈上存放变量i
的地址,create
函数中第一次for
循环在堆上创建一个funcval
结构体,fn
指向闭包函数入口地址,同时捕获列表捕获i
的地址,这样闭包函数就和外层函数操作同一个变量i
create
函数中第一次for
循环在堆上创建一个funcval
结构体,执行完后i=1
,第二次for
循环时会在堆上再次创建一个funcval
结构体,执行完后i=2
因为返回值的地址为funcval
结构体的起始地址,捕获列表中的i
地址指向堆上的变量i
,所以输出为2,2
分析下面一个案例
func adder() func(int) int {
var x int
return func(y int) int {
x += y
return x
}
}
func main() {
var f = adder()
fmt.Println(f(10)) //10
fmt.Println(f(20)) //30
fmt.Println(f(30)) //60
}
这里adder()
被调用一次只创建一个funcval结构体,通过f(10)
调用闭包函数,从而给参数y
赋值,而 x
又在堆上,所以x
一直增加