SUNT的随手记

闲的时候,做一些对自己有益的事

0%

go语言中的闭包

在 Go 语言中,闭包(Closure) 是一个函数值(function value),它可以引用其外部作用域中的变量。即使外部函数已经返回,闭包仍然能够访问和操作那些外部的变量。这意味着闭包“捕获”了外部作用域的变量,并且这些变量的生命周期会随着闭包的存在而延长。

闭包的定义

闭包的本质就是一个函数,它不仅仅有自己的局部变量,还可以访问其外层函数的变量,即使外层函数已经执行完毕,闭包仍然能够“记住”这些外部变量的状态。

Go 语言中的闭包使用示例

示例 1:最简单的闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func main() {
// 闭包:内部函数使用了外部函数的局部变量
counter := createCounter()
fmt.Println(counter()) // 输出:1
fmt.Println(counter()) // 输出:2
fmt.Println(counter()) // 输出:3
}

func createCounter() func() int {
count := 0 // 外部变量
return func() int {
count++ // 闭包引用并修改了外部变量
return count
}
}

详细解释:

  • createCounter() 函数返回了一个匿名函数,该匿名函数是一个闭包。
  • createCounter() 函数中,局部变量 count 被创建。
  • 匿名函数返回后,虽然 createCounter() 函数执行完毕,但由于闭包引用了外部的 count 变量,count 的值被闭包记住了,生命周期被延长。
  • 每次调用闭包时,它都会修改并返回 count 的值。

输出结果为:

1
2
3
1
2
3

示例 2:闭包捕获外部变量的特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func main() {
functions := []func(){}

for i := 0; i < 3; i++ {
// 创建闭包并添加到切片中
functions = append(functions, func() {
fmt.Println(i) // 访问外部的 i
})
}

// 执行切片中的所有函数
for _, fn := range functions {
fn() // 闭包捕获的 i 是 3
}
}

解释:

在这个例子中,我们创建了 3 个闭包,并且它们都被存储在切片 functions 中。注意 i 是外部循环中的变量,闭包捕获的是引用,而不是值。

输出结果为:

1
2
3
3
3
3

所有的闭包打印的值都是 3,这是因为闭包捕获了变量 i 的引用,而不是每次迭代时的具体值。当循环结束后,i 的值变为 3,所以当闭包被调用时,所有闭包引用的都是最终的 i 的值。

修复捕获外部变量的问题

我们可以通过在循环体中引入一个局部变量来修复这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
functions := []func(){}

for i := 0; i < 3; i++ {
value := i // 将 i 的当前值保存到局部变量 value 中
functions = append(functions, func() {
fmt.Println(value) // 捕获 value 而不是 i
})
}

for _, fn := range functions {
fn() // 现在闭包将捕获不同的 value 值
}
}

输出结果为:

1
2
3
0
1
2

解释:

在这个修正后的版本中,我们引入了一个局部变量 value,它在每次循环中存储了当前的 i 值。闭包捕获了 value 的值,因此每个闭包都会打印正确的数值。

闭包的作用

  1. 封装逻辑:可以隐藏函数的内部实现细节,暴露出需要的操作接口。
  2. 状态保持:闭包可以保持外部变量的状态(如上面的计数器例子)。
  3. 延迟执行:闭包可以用作回调函数或延迟执行某些操作。

总结

在 Go 语言中,闭包是指能够捕获并使用其外部环境中变量的函数。闭包可以非常方便地创建具有持久状态的函数(如计数器),并且在异步、回调、状态管理等场景中非常有用。