引言
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中函数闭包的浅显理解,如果哪里有问题希望能够指正,也希望可以对你有所帮助!