Go语言之旅 - 闭包

闭包是什么?

闭包是在词法上下文中引用了自由变量的函数,这种说法可能太过学术化了,很难理解。

用通俗的话来说, 闭包相当于在一个函数中,去捕获自由变量(在函数外部定义但在函数内被引用的变量) 。当脱离了捕获该自由变量的上下文,依旧可以使用该自由变量。

我们来看一个例子

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

小结

  1. Go语言能通过escape analyze识别出变量的作用域,自动将变量在堆上分配。将闭包环境变量在堆上分配是Go实现闭包的基础。

  2. 返回闭包时并不是单纯返回一个函数,而是返回了一个结构体,记录下函数返回地址和引用的环境中的变量地址。

  3. 闭包可以缩小变量作用域,减少对全局变量的污染,可以让我们不用传递参数就可以读取或者修改环境状态

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值