闭包底层分析

闭包

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())
}
运行结果为22

这里的f1f2就称为两个Function value,Function value本质上是一个指针,但是并不是直接指向函数入口,而是指向一个runtime.funcval结构体,这个结构体中只有一个地址fn指向函数指令的入口
在这里插入图片描述
来看下面示图:

在这里插入图片描述

main函数栈帧有两个局部变量f1,f2,然后是返回值空间,add函数中有一个局部变量c = 2,执行时因为f1 := add()add函数会在堆上分配一个funcval结构体,其中fn指向代码段中的闭包函数入口,同时还有一个捕获列表,这里只捕获了变量c,通过f1()调用函数时,这个结构体的起始地址addr2,就被作为返回值写入返回值空间,返回值空间中地址为addr2f1就被赋值为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一直增加

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值