作者简介:
高科,先后在 IBM PlatformComputing从事网格计算,淘米网,网易从事游戏服务器开发,拥有丰富的C++,go等语言开发经验,mysql,mongo,redis等数据库,设计模式和网络库开发经验,对战棋类,回合制,moba类页游,手游有丰富的架构设计和开发经验。 (谢谢你的关注)
----------------------------------------------------------------------------------------------------------------
上一篇文章 【go从入门到精通】匿名函数详解 当时写完,总是感觉意犹未尽,从匿名函数我联想到了另外一个用法————闭包。
Go闭包的实现原理
闭包是指一个函数值(函数对象)可以引用在其外部定义的变量。这些变量可以是在函数内部未定义的,但是该函数可以访问和操作这些变量。
闭包和匿名函数似乎有些像。目前在JavaScript、Go、PHP、Scala、Scheme、Common Lisp、Smalltalk、Groovy、Ruby、 Python、Lua、objective c、Swift 以及Java8以上等语言中都能找到对闭包不同程度的支持。通过支持闭包的语法可以发现一个特点,他们都有垃圾回收(GC)机制。
在go中, 闭包的实现取决于函数的返回类型。具体来说,如果一个函数返回一个函数类型,那么该函数就是一个闭包。
常见使用时机:延长区域变数的生命周期,不会被GC吃掉。
package main
import "fmt"
var foo = func() func() {
var i int
return func() {
i++
fmt.Println(i)
}
}()
func main() {
foo() // 1
foo() // 2
foo() // 3
}
输出结果是:
1
2
3
在闭包的使用上,不仅可以设置函式回传函式,也可以设置函式回传struct等其他类型状态。
package main
import "fmt"
type counter struct {
add func()
minus func()
print func()
}
func main() {
count := func() counter {
idx := 0
return counter{
func() { idx++ },
func() { idx-- },
func() { fmt.Println("idx =", idx) },
}
}()
count.add()
count.print() // idx = 1
count.add()
count.print() // idx = 2
count.minus()
count.print() // idx = 1
}
闭包函数及其引用环境共同构成一个struct,其中函数部分是闭包函数的代码,引用环境包含闭包函数访问的外部变量。该结构由编译器自动创建,并在调用时传递给闭包函数。
闭包函数与其引用环境之间的连接是通过指针实现的。当闭包函数引用外部变量时,编译器在引用环境中查找该变量并返回其地址。该地址存储在闭包函数内部,并在调用闭包函数时使用。
我们通过下面代码来看看闭包的使用:
package main
import "fmt"
var G int = 7
func main() {
// 影响全局变量G,代码块状态持续
y := func() int {
fmt.Printf("G: %d, G的地址:%p\n", G, &G)
G += 1
return G
}
fmt.Println(y(), y)
fmt.Println(y(), y)
fmt.Println(y(), y) //y的地址
// 影响全局变量G,注意z的匿名函数是直接执行,所以结果不变
z := func() int {
G += 1
return G
}()
fmt.Println(z, &z)
fmt.Println(z, &z)
fmt.Println(z, &z)
// 影响外层(自由)变量i,代码块状态持续
var f = N()
fmt.Println(f(1), &f)
fmt.Println(f(1), &f)
fmt.Println(f(1), &f)
var f1 = N()
fmt.Println(f1(1), &f1)
}
func N() func(int) int {
var i int
return func(d int) int {
fmt.Printf("i: %d, i的地址:%p\n", i, &i)
i += d
return i
}
}
程序输出:
G: 7, G的地址:0x1547368
8 0xf98e40
G: 8, G的地址:0x1547368
9 0xf98e40
G: 9, G的地址:0x1547368
10 0xf98e40
11 0xc00025a1c0
11 0xc00025a1c0
11 0xc00025a1c0
i: 0, i的地址:0xc00025a1c8
1 0xc0000a8498
i: 1, i的地址:0xc00025a1c8
2 0xc0000a8498
i: 2, i的地址:0xc00025a1c8
3 0xc0000a8498
i: 0, i的地址:0xc00025a1d0
1 0xc0000a84a0
首先强调一点,G是闭包中被捕获的全局变量,因此,对于每一次引用,G的地址都是固定的,i是函数内部局部变量,地址也是固定的,他们都可以被闭包保持状态并修改。还要注意,f和f1是不同的实例,它们的地址是不一样的。
闭包的陷阱
闭包就像是一个魔术,可以在局部变量的作用域消失后很长一段时间内保留它们。 因为它所做的实际上是返回另一个函数,该函数包装了我们打算保留的变量。我们可以继续在全局范围内使用返回函数中“包含”的那些变量。
// Closure function in Go
func willReturnClosure() func() int {
var counter int = 0
return func() int {
counter += 1
return counter
}
}
func main() {
c := willReturnClosure()
for i := 0; i < 10; i++ {
fmt.Println(c())
}
}
在c()函数中,我们对 func() 的调用将返回局部变量counter,并保持变量的增量状态直到下一次调用,就像你正在处理全局变量一样。
现在,问题是,如果上面的闭包存储在全局变量中,假设我们像这样存储willReturnClosure函数:
// Closure function in Go
func willReturnClosure() func() int {
var counter int = 0
return func() int {
counter += 1
return counter
}
}
var c = willReturnClosure()
func main() {
for i := 0; i < 10; i++ {
fmt.Println(c())
}
}
与其他全局变量不同,编译器永远不会知道您何时会使用完计数器,并且它永远不会被 GC 释放。它会像《盗梦空间》电影一样徘徊在你的程序的地狱边缘,直到你的全局作用域退出。 应小心处理此内存泄漏。
标签:闭包,入门,int,fmt,Println,func,go,函数 From: https://blog.csdn.net/pbymw8iwm/article/details/136915084