返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

4.1 匿名函数

发布于 2024-10-12 19:15:47 字数 4836 浏览 0 评论 0 收藏 0

匿名函数是指没有名字符号的函数。除此之外,和普通函数类似。

  • 可在函数内定义匿名函数,形成嵌套。
  • 可随定义直接传参调用,并返回结果。
  • 可保存到变量、结构字段,或作为参数、返回值传递。
  • 匿名函数可引用环境变量,形成闭包。
  • 不曾使用的匿名函数被当作错误。
  • 不能使用编译指令。( //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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文