跳转到主要内容
模块 3 · 第 4 课(共 5)课程中的14/50~12 min
模块课程(4/5)

闭包和函数作为值

在 Go 中,函数是一流的值:它们可以分配给 变量,传递给其他函数,然后返回。 闭包是 捕获变量的函数文字(匿名) 定义它的环境。

函数字面量

一个没有名字的函数,内联编写。您立即调用它或 分配它:

Go
greet := func(name string) {
    fmt.Println("ciao", name)
}
greet("Anna")

// invocata immediatamente:
func() { fmt.Println("subito") }()

greet 的类型是 func(string)。

捕获环境:关闭

函数文字可以读取和修改中可见的变量 它的词法范围,即使在外部函数返回之后:

Go
func counter() func() int {
    n := 0
    return func() int {
        n++
        return n
    }
}

c := counter()
fmt.Println(c(), c(), c())  // 1 2 3

只要至少有一个闭包引用它,n 就“存在”。每次致电 counter() 创建一个新的、独立的 n。

关闭作为回调

一个非常常见的模式:

Go
nums := []int{3, 1, 2}
sort.Slice(nums, func(i, j int) bool {
    return nums[i] < nums[j]
})

闭包捕获 nums 并定义顺序。

函数作为参数

要接受函数作为参数,请写出其类型:

Go
func apply(nums []int, f func(int) int) []int {
    out := make([]int, len(nums))
    for i, n := range nums {
        out[i] = f(n)
    }
    return out
}

double := func(x int) int { return x * 2 }
apply([]int{1, 2, 3}, double)   // [2 4 6]

如果签名变得难以阅读,请定义一个类型别名:

Go
type Transform func(int) int
func apply(nums []int, f Transform) []int { ... }

尝试一下

锻炼#go.m3.l4.e1
尝试:0加载中...

定义返回闭包的 counter():每次调用闭包都会递增并返回一个内部计数器。

正在加载编辑器...
显示提示

`n` 必须在返回之前声明;闭包捕获了它。

3 次尝试后可用的解决方案

锻炼#go.m3.l4.e2
尝试:0加载中...

为 f 分配一个打印“ciao”的函数文字,然后调用它。

正在加载编辑器...
显示提示

语法:`f := func() { ... }` 然后是 `f()`。

3 次尝试后可用的解决方案

Quiz#go.m3.l4.e3
准备好

这打印什么? (注意闭包范围)

Go
mk := func() func() int {
    x := 0
    return func() int { x++; return x }
}
a := mk()
b := mk()
fmt.Println(a(), a(), b())
答案选项

回顾

  • 函数是一流的:可分配、可传递、可返回。
  • 函数文字:func(params) ret { ... },匿名。
  • 闭包:捕获词法作用域的变量,使它们保持活动状态。
  • 参数中的显式函数类型:func(int) int。
  • 从 Go 1.22 开始,每个 for 迭代都有自己的循环变量副本。