golang关于闭包

Golang:“闭包(closure)”到底包了什么?
golang 闭包
Golang中闭包的理解

什么是闭包呢?摘用Wikipedia上的一句定义:

a closure is a record storing a function together with an environment.
闭包是由函数和与其相关的引用环境组合而成的实体 。

因此闭包的核心就是:函数和环境。其实这里就已经可以回答本文题目的问题:闭包究竟包了什么?答案是:函数和环境。但是相信部分看官们到这里依然不清楚:什么函数?什么环境?

函数,指的是在闭包实际实现的时候,往往通过调用一个外部函数返回其内部函数来实现的。内部函数可能是内部实名函数、匿名函数或者一段lambda表达式。用户得到一个闭包,也等同于得到了这个内部函数,每次执行这个闭包就等同于执行内部函数。

环境,Wikipedia上说是与其(函数)相关的引用环境,可以说解释地很精准了。具体地说,在实际中引用环境是指外部函数的环境,闭包保存/记录了它产生时的外部函数的所有环境。但是这段话对于尚未理解闭包的同学来说依旧是不友好的,听完还是懵懂的。这里尝试做个更实用性的解释:

  • 如果外部函数的所有变量可见性都是local的,即生命周期在外部函数结束时也结束的,那么闭包的环境也是封闭的。
  • 反之,那么闭包其实不再封闭,全局可见的变量的修改,也会对闭包内的这个变量造成影响。
    ps: 应用上就是如果闭包引用的变量是指针类型,则外部改变此变量,闭包内同样能感知到变化。

【总结】:

  1. 闭包是环境和变量的结合体
  2. go中常使用匿名函数且匿名函数体内引用外部变量形式,生成闭包
  3. 闭包内的变量具有延迟绑定特点,在真正执行时才获取到值
  4. 使用go +匿名函数的形式启动新goutine,如果也使用了闭包,同样具有变量延迟绑定的特点
  5. 结合4和5,只要函数不是立即执行的,函数内引用的外部变量,在执行时都有可能和声明时不同,可以统一理解为延迟执行,defer语句也有这个特点。

以下是几个例子:

// foo1 内部隐藏变量*int类型变量,函数外的*int变化会影响函数内
func foo1(x *int) func() {
	return func() {
		*x = *x + 1
		fmt.Printf("foo1 val = %d\n", *x)
	}
}

// foo2 内部隐藏x变量,但由于go函数传参是值复制,调用foo2时x是一个值的拷贝,因此函数外实参的变化不会再影响生成的闭包函数
func foo2(x int) func() {
	return func() {
		x = x + 1
		fmt.Printf("foo2 val = %d\n", x)
	}
}

//foo3 直接打印range遍历的val,val变量的地址不变
func foo3() {
	values := []int{1, 2, 3, 5}
	for _, val := range values {
		fmt.Printf("foo3 val=%d\n", val)
	}
}

func show(v int) {
	fmt.Printf("foo4 val=%d\n", v)
}
// foo4 使用goroutine将val作为参数,而将val作为参数时,传递的就是每次遍历的值,不会延迟绑定
func foo4() {
	values := []int{1, 2, 3, 5}
	for _, val := range values {
		go show(val)
	}
	time.Sleep(3 * time.Second)
}

// foo5 使用goroutine但没有传参,而是用闭包的形式,这时候延迟绑定,val的值只有在计算时才获取
func foo5() {
	values := []int{1, 2, 3, 5}
	for _, val := range values {
		go func() {
			fmt.Printf("foo5 val=%d\n", val) /var变量的地址不变,延迟绑定最后打印的值相同都是5
		}()
	}
	time.Sleep(3 * time.Second)
}

// foo6 同foo5相同
var foo6Chan = make(chan int, 10)
func foo6() {
	for val := range foo6Chan {
		//go func() {
		//	fmt.Printf("foo6 val=%d\n", val) // 和foo5的slice一样,val变量的地址不变,延迟绑定导致最后打印的值相同
		//}()
		fmt.Printf("foo6 val=%d\n", val)
	}
	time.Sleep(3 * time.Second)
}

func foo7(x int) []func() {
	var fs []func()
	values := []int{1, 2, 3, 5}
	for _, val := range values {
		fs = append(fs, func(){
			// 这里匿名函数内部引用外部的变量x和val,x不变,但val在变化
			fmt.Printf("foo7 val=%d\n", x+val)
		})
	}
	return fs
}

func main() {
	i := 0
	f := foo1(&i)
	f() // 1
	f() // 2

	i = 100
	f() // 101
	f() // 102


	i2 := 0
	f2 := foo2(i2)
	f2() // 1
	f2() // 2
	i2 = 100
	f2() // 3
	f2() // 4

	foo3() // 1 2 3 5
	foo4() // 1 5 2 3
	foo5() // 5 5 5 5

	foo6chan <- 1
	foo6chan <- 2
	foo6chan <- 3
	foo6chan <- 4
	foo6chan <- 5
	close(foo6chan)
	foo6()

	fs := foo7(1)
	for _, f := range fs {
		f()
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值