什么是闭包?什么场景下会用闭包?
本文对go 语言中的闭包做了详细介绍。
闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。
go中的闭包 闭包是函数式语言中的概念,没有研究过函数式语言的用户可能很难理解闭包的强大,相关的概念超出了本书的范围。go语言是支持闭包的,这里只是简单地讲一下在go语言中闭包是如何实现的。
func f(i int) func() int { return func() int { i++ return i }} 函数f返回了一个函数,返回的这个函数,返回的这个函数就是一个闭包。这个函数中本身是没有定义变量i的,而是引用了它所在的环境(函数f)中的变量i。
c1 := f(0)c2 := f(0)c1() // reference to i, i = 0, return 1c2() // reference to another i, i = 0, return 1 c1跟c2引用的是不同的环境,在调用i++时修改的不是同一个i,因此两次的输出都是1。函数f每进入一次,就形成了一个新的环境,对应的闭包中,函数都是同一个函数,环境却是引用不同的环境。
变量i是函数f中的局部变量,假设这个变量是在函数f的栈中分配的,是不可以的。因为函数f返回以后,对应的栈就失效了,f返回的那个函数中变量i就引用一个失效的位置了。所以闭包的环境中引用的变量不能够在栈上分配。
escape analyze 在继续研究闭包的实现之前,先看一看go的一个语言特性:
func f() *cursor { var c cursor c.x = 500 noinline() return &c} cursor是一个结构体,这种写法在c语言中是不允许的,因为变量c是在栈上分配的,当函数f返回后c的空间就失效了。但是,在go语言规范中有说明,这种写法在go语言中合法的。语言会自动地识别出这种情况并在堆上分配c的内存,而不是函数f的栈上。
为了验证这一点,可以观察函数f生成的汇编代码:
movq $type..cursor+0(sb),(sp) // 取变量c的类型,也就是cursorpcdata $0,$16pcdata $1,$0call ,runtime.new(sb) // 调用new函数,相当于new(cursor)pcdata $0,$-1movq 8(sp),ax // 取c.x的地址放到ax寄存器movq $500,(ax) // 将ax存放的内存地址的值赋为500movq ax,.~r0+24(fp)addq $16,sp 识别出变量需要在堆上分配,是由编译器的一种叫escape analyze的技术实现的。如果输入命令:
go build --gcflags=-m main.go 可以看到输出:
./main.go moved to heap: c./main.go &c escapes to heap 表示c逃逸了,被移到堆中。escape analyze可以分析出变量的作用范围,这是对垃圾回收很重要的一项技术。
闭包结构体 回到闭包的实现来,前面说过,闭包是函数和它所引用的环境。那么是不是可以表示为一个结构体呢:
type closure struct { f func()() i *int} 事实上,go在底层确实就是这样表示一个闭包的。让我们看一下汇编代码:
func f(i int) func() int { return func() int { i++ return i }}movq $type.int+0(sb),(sp)pcdata $0,$16pcdata $1,$0call ,runtime.new(sb) // 是不是很熟悉,这一段就是i = new(int) ... movq $type.struct { f uintptr; a0 *int }+0(sb),(sp) // 这个结构体就是闭包的类型...call ,runtime.new(sb) // 接下来相当于 new(closure)pcdata $0,$-1movq 8(sp),axnop ,movq $.func·001+0(sb),bpmovq bp,(ax) // 函数地址赋值给closure的f部分nop ,movq .&i+16(sp),bp // 将堆中new的变量i的地址赋值给closure的值部分movq bp,8(ax)movq ax,.~r1+40(fp)addq $24,spret , 其中func·001是另一个函数的函数地址,也就是f返回的那个函数。
小结 go语言支持闭包 go语言能通过escape analyze识别出变量的作用域,自动将变量在堆上分配。将闭包环境变量在堆上分配是go实现闭包的基础。 返回闭包时并不是单纯返回一个函数,而是返回了一个结构体,记录下函数返回地址和引用的环境中的变量地址。 转自:
tiancaiamao.gitbooks.io/go-internals/content/zh/03.6.html
ModbusTCP转EtherNetIP网关连接昆仑通态触摸屏案例
如何调节恒温恒湿试验箱的风速,有哪些方法
丰田继续加码机器人与无人驾驶,追加以色列投资量
程序员写代码需要有什么样的好习惯
钢二柱散热器的简单介绍
详细介绍go语言中的闭包的实现
MAX4940, MAX4940A 高度集成的四通道/双通道
建立公链生态领航力量 Ulord让区块链价值真正落地
浅谈晶振选择的那些事
pcba板的基础知识_pcba板生产流程
半导体行业需要关注的静电问题及解决方案
C语言的进阶操作实例讲解
勒索病毒变种卷土重来:伪装《王者荣耀》辅助工具对准安卓机下手
匈牙利电动汽车投资激增 比亚迪或在当地建厂
影响电动车蓄电池循环使用寿命的重要因素
华为鸿蒙系统支持机型 哪些机型能升级鸿蒙系统
谷歌发布12月Android安全补丁修复安全漏洞
PN8368 5V1.5A高精度低功耗电源芯片
936型恒温电烙铁电子电路设计图
关于商用智能手机在5G现网中的作用分析和介绍