上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
4.1 匿名函数
匿名函数是指没有名字符号的函数。除此之外,和普通函数类似。
- 可在函数内定义匿名函数,形成嵌套。
- 可随定义直接传参调用,并返回结果。
- 可保存到变量、结构字段,或作为参数、返回值传递。
- 匿名函数可引用环境变量,形成闭包。
- 不曾使用的匿名函数被当作错误。
- 不能使用编译指令。(
//go:noinline
)
func main() { // 定义时调用。 _ = func(s string) string { return "[" + s + "]" }("abc") // -------------- // 变量。 add := func(x, y int) int { return x + y } _ = add(1, 2) }
// 参数和返回值。 func wrap(f func(string)string) func() { return func() { println(f("abc")) } } func main() { wrap(func(s string) string { return "[" + s + "]" })() }
func main() { // func(s string) { println(s) } // ~~~~ func is not used }
编译器为匿名函数自动生成名字。
func main() { func() { println("abc") }() } /* $ go build -gcflags "-l" $ go tool objdump -S -s "main\.main" ./test CALL main.main.func1(SB) */
普通函数和匿名函数都可作为结构体字段,或经通道传递。
func main() { calc := struct { add func(int, int) int mul func(int, int) int }{ add: func(x, y int) int { return x + y }, mul: func(x, y int) int { return x * y }, } _ = calc.add(1, 2) }
func main() { fn := make(chan func()) go func() { defer close(fn) f := <- fn f() }() fn <- func(){ println("hi!") } <- fn }
匿名函数是常见的重构手段:将大段代码分解成多个相对独立的逻辑单元。
- 不提升代码作用域的前提下,分离流程和细节。
- 依赖函数接口而非具体代码,逻辑层次更清晰。
- 无状态逻辑与有状态变量分离,更利于测试。
- 作用域隔离,修改代码时不会引发外部污染。
闭包
闭包(closure)是匿名函数和其引用的外部环境变量组合体。
//go:noinline func test() func() { x := 100 println(&x, x) return func() { // 返回闭包,而非函数。 x++ println(&x, x) // 引用环境变量 x 。 } } func main() { test()() } /* 0xc000018060 100 0xc000018060 101 */
实现
从上例输出结果看,闭包会延长环境变量生命周期。
所谓闭包,实质上是由匿名函数和(一到多个)环境变量指针构成的结构体。
提示:编译器优化可能改变闭包返回和执行方式,以实际输出为准。
/* $ go build -gcflags "-m -S" moved to heap: x ---- func test --------------------- TEXT "".test(SB), ABIInternal x := 100 LEAQ type.int(SB), AX CALL runtime.newobject(SB) MOVQ AX, "".&x+16(SP) MOVQ $100, (AX) return func(){...} LEAQ type.noalg.struct { F uintptr; "".x *int }(SB), AX CALL runtime.newobject(SB) LEAQ "".test.func1(SB), CX ; 匿名函数。 MOVQ CX, (AX) MOVQ "".&x+16(SP), CX ; 环境变量 x 地址。 MOVQ CX, 8(AX) RET */
接收到的闭包,经专用寄存器(DX),将环境变量地址传递给匿名函数。
/* --- func main ---------------------- TEXT "".main(SB), ABIInternal CALL "".test(SB) ; 返回闭包。 MOVQ (AX), CX ; 匿名函数。 MOVQ AX, DX ; 传递给匿名函数。 CALL CX ; 调用匿名函数。 --- func test.func1 ---------------------- TEXT "".test.func1(SB), NEEDCTXT|ABIInternal MOVQ 8(DX), AX ; 从闭包中获取环境变量。 MOVQ AX, "".&x+8(SP) CALL runtime.printlock(SB) MOVQ "".&x+8(SP), AX CALL runtime.printpointer(SB) CALL runtime.printnl(SB) CALL runtime.printunlock(SB) RET */
从实现机制看,拿到手的闭包除非执行,否则只是存有两个指针的结构体。
这会导致 “延迟求值”,一个初学者时常出错的问题。
func test() (s []func()) { for i := 0; i < 2; i++ { // 循环使用的 i 始终是同一变量。 // 闭包(存储 i 指针)被添加到切片内。 s = append(s, func() { println(&i, i) }) } return } func main() { for _, f := range test() { // 执行闭包中的匿名函数。 // 它以指针读取环境变量 i 的值。 // 自然是最后一次循环时 i = 2。 f() } } // 0xc000018060 2 // 0xc000018060 2
func test() (s []func()) { for i := 0; i < 2; i++ { // 用不同的环境变量。 x := i s = append(s, func() { println(&x, x) }) } return } func main() { for _, f := range test() { f() } } // 0xc000082000 0 // 0xc000082008 1
应用
静态局部变量。
func test() func() { n := 0 return func() { n++ println("call:", n) } } func main() { f := test() f() f() f() } /* call: 1 call: 2 call: 3 */
模拟方法,绑定状态。
func handle(db Database) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { url := db.Get() io.WriteString(w, url) } } func main() { db := NewDatabase("localhost:5432") http.HandleFunc("/url", handle(db)) http.ListenAndServe(":3000", nil) }
包装函数,改变其签名或增加额外功能。
package main import ( "fmt" "time" ) // 修改签名。 func partial(f func(int), x int) func() { return func() { f(x) } } // 增加功能。 func proxy(f func()) func() { return func() { n := time.Now() defer func() { fmt.Println(time.Now().Sub(n)) }() f() } } func main() { test := func(x int) { println(x) } var f func() = partial(test, 100) f() proxy(f)() } // 100 // 100 // 16.1µs
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论