对函数闭包的简单理解

引言

go语言支持函数闭包,之前没有了解过,第一次接触感觉挺神奇的,简单记录一下自己的理解;

闭包概念

引用网上的解释:Go语言中闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量,因此,简单的说: 函数 + 引用环境 = 闭包;

这里主要提到有两点:自由变量和函数;

在go语言中闭包一般通过匿名函数实现,所以这个函数就是指匿名函数,而自由变量就是可以简单理解为匿名函数中调用的变量,但是这个变量的声明却是在匿名函数之外的;

闭包理解

只看概念可能理解不清楚,这里我先使用一个例子来说明;

先看代码:

package main

import "fmt"

func showInfo(str string) func() string {
    // 匿名函数
	return func() string {
		return str
	}
}

func main() {
	function01 := showInfo("hello")
	fmt.Println("function01为:", function01())

	function02 := showInfo("world")
	fmt.Println("function02为:", function02())
}

执行结果为:

function01为: hello
function02为: world

有没有发现一个问题:可以通过function01或者function02访问到str变量,但是str变量却是存在于showInfo函数中,且使用function01或者function02时showInfo函数已经调用结束,那么按常理来说该函数内的局部变量都应该被释放掉了,但是为什么function01或者function02依然可以访问呢?

在main函数中调用过程大致如下:

先是function01:

  • 调用showInfo函数,创建function01闭包
  • showInfo函数已经调用结束,但是字符串str变量被闭包捕获,仍然可以通过函数变量function01访问到该str变量

然后是function02:

  • 调用showInfo函数,创建function02闭包,function02是一个新的闭包,和function01无关
  • showInfo函数已经调用结束,但是字符串str变量被闭包捕获,仍然可以通过函数变量function02访问到该str变量

这里就是匿名函数和局部变量str形成了闭包,通过闭包的操作从而可以使得局部变量在函数外访问成为可能;

所以闭包又可以这样解释:匿名函数封闭并包围作用域中的变量;(这里就是匿名函数封闭包围了str变量)
注意:闭包保存的是周围变量的引用而不是副本值,所以修改被闭包捕获的变量时会影响调用匿名函数的结果

这只是对闭包的一个简单的理解,我们再看一个例子;

代码如下:

package main

import "fmt"

func addTest() func(a int) int {
	// 下面部分构成闭包,且调用count值保留上次匿名函数的修改结果
	var count int
	fmt.Println("count的初始地址为:", &count) // 打印count地址,可以判断每次调用时是否使用的是同一个count
	return func(num int) int {
		fmt.Println("count的在匿名函数内的地址为:", &count)
		count += num
		return count
	}
}

func main() {
	function01 := addTest()
	for i := 1; i <= 3; i++ {
		fmt.Println(function01(i)) // count持续加
	}

	fmt.Println("==================")

	function02 := addTest()
	for i := 1; i <= 3; i++ {
		fmt.Println(function02(i)) // count持续加
	}
}

执行结果为:

count的初始地址为: 0xc000016098
count的在匿名函数内的地址为: 0xc000016098
1                                         
count的在匿名函数内的地址为: 0xc000016098
3                                         
count的在匿名函数内的地址为: 0xc000016098
6                                         
==================                        
count的初始地址为: 0xc0000160d0          
count的在匿名函数内的地址为: 0xc0000160d0
1                                         
count的在匿名函数内的地址为: 0xc0000160d0
3                                         
count的在匿名函数内的地址为: 0xc0000160d0
6

我们可以再分析一下,这里闭包是由局部变量count和匿名函数构成;这里输出了count的地址,可以发现在一个闭包中不论这个闭包调用多少次count的地址是不会改变的,只有不同的闭包count地址才不同;

这里就从地址层面理解一下:

对于function01:

  • function01就是一个闭包,function01保存了对count的引用(指针指向count),所以可以通过function01实现对count的修改

对于function02:

  • function02是一个新的闭包,所以function02保存了新的count的引用,和function01无关

所以通过输出结果观察count地址可以判断内部匿名函数是对外部变量的引用


如果到这里还是不理解闭包,但是之前学过java或者面向对象思想,可以这样理解(只用来理解,不能用于解释闭包):

可以把addTest函数当成一个来看,那么count就是属性,匿名函数就类似方法
function01 := addTest()就相当于创建一个类的实例function01,
通过该实例function01调用匿名函数(方法)修改count属性值,每次修改后count属性值都会保留,并不会重置;
一旦创建了一个新的实例function02 := addTest(),就相当于新的实例,那么对应的count属性值也是初始值

func addTest() func(a int) int {
	// count可以看作是addTest函数中的全局变量,所以每次调用匿名函数后修改count值,
	var count int // 理解为类的属性
	fmt.Println("count的初始地址为:", &count)
    // 匿名函数(可以理解为类的方法)
	return func(num int) int {
		fmt.Println("count的在匿名函数内的地址为:", &count)
		count += num
		return count
	}
}

我开始是这样简单的理解了一下才逐渐明白闭包的使用的,所以这种理解仅在使用层面上有点帮助;

总结

这是我对go中函数闭包的浅显理解,如果哪里有问题希望能够指正,也希望可以对你有所帮助!

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YXXYX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值