闭包
闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
-- wikipedia
闭包包含两部分:
- 函数本身
- 函数所引用的环境
Go
里闭包的函数必须是匿名函数。
/**
闭包:是引用了自由变量的函数。这个被引用的自由变量和这个函数一同存在,即使已经离开了创建的环境
*/
func myFunc() func() int {
foo := 0
//myFunc 里面的匿名函数可以访问并且更新 myFunc 里面的变量,这个变量的生命周期因为匿名函数的存在而延长。
return func() int {
foo++
return foo
}
}
func main() {
bar := myFunc()
value_1 := bar()
value_2 := bar()
println(value_1)
println(value_2)
}
运行结果:
D:\GoWorkspace\src\polaris\main>go run closures.go
1
2
使用闭包实现斐波那契额
package main
/**
使用闭包实现斐波那契额
*/
func makeFibGen() func() int {
f1 := 0
f2 := 1
return func() (fib int) {
fib = f1
f2, f1 = (f1 + f2), f2
return fib
}
}
func main() {
gen := makeFibGen()
for i := 0; i < 10; i++ {
println(gen())
}
}
Go 中匿名函数的实现
package main
/**
匿名函数的实现
*/
func myFunc1(message int) {
println(message)
}
func main() {
f := func(message int) {
println(message)
}
f(0x100)
myFunc1(0x100)
}
编译:
D:\GoWorkspace\src\polaris\main>go build -gcflags "-N -l -m" -o test2
# _/D_/GoWorkspace/src/polaris/main
.\test2.go:11:7: func literal does not escape
然后我们通过 go 提供的反汇编工具,反编译我们刚刚生成的 test 文件。
D:\GoWorkspace\src\polaris\main>go tool objdump -s "main\.main" ./test2
TEXT main.main(SB) D:/GoWorkspace/src/polaris/main/test2.go
test2.go:10 0x45ad00 65488b0c2528000000 MOVQ GS:0x28, CX
test2.go:10 0x45ad09 488b8900000000 MOVQ 0(CX), CX
test2.go:10 0x45ad10 483b6110 CMPQ 0x10(CX), SP
test2.go:10 0x45ad14 7642 JBE 0x45ad58
test2.go:10 0x45ad16 4883ec18 SUBQ $0x18, SP
test2.go:10 0x45ad1a 48896c2410 MOVQ BP, 0x10(SP)
test2.go:10 0x45ad1f 488d6c2410 LEAQ 0x10(SP), BP
test2.go:11 0x45ad24 488d159d000200 LEAQ go.func.*+77(SB), DX
test2.go:11 0x45ad2b 4889542408 MOVQ DX, 0x8(SP)
test2.go:14 0x45ad30 48c7042400010000 MOVQ $0x100, 0(SP)
test2.go:14 0x45ad38 488b0589000200 MOVQ go.func.*+77(SB), AX
test2.go:14 0x45ad3f ffd0 CALL AX
test2.go:15 0x45ad41 48c7042400010000 MOVQ $0x100, 0(SP)
test2.go:15 0x45ad49 e852ffffff CALL main.myFunc1(SB)
test2.go:16 0x45ad4e 488b6c2410 MOVQ 0x10(SP), BP
test2.go:16 0x45ad53 4883c418 ADDQ $0x18, SP
test2.go:16 0x45ad57 c3 RET
test2.go:10 0x45ad58 e81373ffff CALL runtime.morestack_noctxt(SB)
test2.go:10 0x45ad5d eba1 JMP main.main(SB)
:-1 0x45ad5f cc INT $0x3
TEXT main.main.func1(SB) D:/GoWorkspace/src/polaris/main/test2.go
test2.go:11 0x45ad60 65488b0c2528000000 MOVQ GS:0x28, CX
test2.go:11 0x45ad69 488b8900000000 MOVQ 0(CX), CX
test2.go:11 0x45ad70 483b6110 CMPQ 0x10(CX), SP
test2.go:11 0x45ad74 7635 JBE 0x45adab
test2.go:11 0x45ad76 4883ec10 SUBQ $0x10, SP
test2.go:11 0x45ad7a 48896c2408 MOVQ BP, 0x8(SP)
test2.go:11 0x45ad7f 488d6c2408 LEAQ 0x8(SP), BP
test2.go:12 0x45ad84 e84731fdff CALL runtime.printlock(SB)
test2.go:12 0x45ad89 488b442418 MOVQ 0x18(SP), AX
test2.go:12 0x45ad8e 48890424 MOVQ AX, 0(SP)
test2.go:12 0x45ad92 e80939fdff CALL runtime.printint(SB)
test2.go:12 0x45ad97 e8d433fdff CALL runtime.printnl(SB)
test2.go:12 0x45ad9c e8bf31fdff CALL runtime.printunlock(SB)
test2.go:13 0x45ada1 488b6c2408 MOVQ 0x8(SP), BP
test2.go:13 0x45ada6 4883c410 ADDQ $0x10, SP
test2.go:13 0x45adaa c3 RET
test2.go:11 0x45adab e8c072ffff CALL runtime.morestack_noctxt(SB)
test2.go:11 0x45adb0 ebae JMP main.main.func1(SB)
一共有三次 CALL
, 排除调最后那个 runtime
的 CALL
,剩下两次分别对应了匿名函数调用以及正常的函数调用。而两次的区别在于正常的函数是 CALL main.myFunc(SB)
, 匿名函数的调用是 CALL BX
。这两种不同的调用方式意味着什么?我们可以通过 gdb 来动态的跟踪这段代码来具体分析一下。
注意事项
- 匿名函数作为返回对象性能上要比正常的函数性能要差。
- 闭包可能会导致变量逃逸到堆上来延长变量的生命周期,给 GC 带来压力。
参考:
http://sunisdown.me/closures-in-go.html
https://gobyexample.com/closures
不要小瞧女程序员
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
2019-08-22 wetty--web终端