在很多的开源项目里,经常看到闭包的运用,在 Go语言中,函数类型是一种特殊的类型,函数类型可以像其他类型一样被声明、赋值给变量、作为参数传递。进而有了匿名函数、闭包。本文将简要记录闭包的概念和一些常用的场景。
什么是闭包
Go函数可以是闭包。闭包是一个函数值,它从函数体外部引用变量。函数可以访问被引用的变量并对其赋值;从这个意义上说,函数被"绑定"到变量上。-- a Tour of Go
一个最简单的例子: 1~10的加法
go
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos := adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
)
}
}
//OUTPUT
//0
//1
//3
//6
//10
//15
//21
//28
//36
//45
上例中,adder函数返回一个闭包。在闭包内部,它引用了外部的变量sum
。
闭包和匿名函数的区别
匿名函数是指在代码中没有明确命名的函数,闭包是一个函数值,它可以访问其词法环境中捕获的变量。匿名函数可以是闭包,也可以不是闭包。匿名函数引用了外部作用域中的变量,它就成为闭包。
go
func main() {
x := 10
// 匿名函数,不是闭包
anonymousFunc := func() {
fmt.Println("匿名函数:", x)
}
anonymousFunc()
// 闭包
closureFunc := func() {
fmt.Println("闭包:", x)
}
closureFunc()
}
常见的应用场景
闭包在实际编程中具有广泛的应用。以下是几个常见的应用场景:
- 保存状态: 通过捕获外部变量,闭包可以在函数调用之间保留状态信息,如迭代器
- 函数工厂: 根据不同的配置参数来动态创建函数,
- 回调函数:将一个函数作为参数传递给另一个函数,通过闭包,捕获一些上下文信息并执行该函数
- 并发编程:可以安全地在多个goroutine中共享和修改变量,一种简洁的方式
保存状态 -- 迭代器
go
package main
import "fmt"
func Iterator(values []int) func() (int, bool) {
index := 0
return func() (int, bool) {
if index >= len(values) {
return 0, false
}
value := values[index]
index++
return value, true
}
}
func main() {
numbers := []int{1, 2, 3, 4, 5}
iterate := Iterator(numbers)
for {
value, ok := iterate()
if !ok {
break
}
fmt.Println(value)
}
}
//OUTPUT:
//1
//2
//3
//4
//5
上例中,index为闭包的共享状态。Iterator创建一个闭包函数,这个闭包函数用于迭代切片中的元素。其中,闭包每次执行,index会递增,直到循环结束。比如在很多开源框架的中间件中,会使用迭代器模式实现。
函数工厂
go
package main
import "fmt"
func FunctionFactory(operation string) func(int, int) int {
switch operation {
case "add":
return func(a, b int) int {
return a + b
}
case "subtract":
return func(a, b int) int {
return a - b
}
case "multiply":
return func(a, b int) int {
return a * b
}
case "divide":
return func(a, b int) int {
if b != 0 {
return a / b
}
return 0
}
default:
return nil
}
}
func main() {
addFunc := FunctionFactory("add")
subtractFunc := FunctionFactory("subtract")
multiplyFunc := FunctionFactory("multiply")
divideFunc := FunctionFactory("divide")
fmt.Println(addFunc(5, 3))
fmt.Println(subtractFunc(10, 2))
fmt.Println(multiplyFunc(4, 5))
fmt.Println(divideFunc(10, 2))
fmt.Println(divideFunc(10, 0))
}
//OUTPUT:
8
8
20
5
0
上例中,FunctionFactory分别提供加法、减法、乘法和除法的闭包函数,并封装为函数工厂,使得函数的创建更加灵活和可定制。
回调函数
go
package main
import (
"fmt"
"time"
)
func PerformOperationAsync(input int, callback func(int)) {
go func() {
time.Sleep(2 * time.Second)
result := input * 2
callback(result)
}()
}
func main() {
callback := func(result int) {
fmt.Println("操作结果:", result)
}
PerformOperationAsync(5, callback)
time.Sleep(3 * time.Second)
}
//OUTPUT
//操作结果:10
上例中,使用匿名函数创建了一个闭包callback
,即回调函数。在执行异步操作时,它会将计算结果result
传递给回调函数。比如,我们需要等待某个长时间的操作或者某个事件触发之后的场景。
并发编程
go
package main
import (
"fmt"
"sync"
"time"
)
func ConcurrentTask(tasks []func() int) []int {
results := make([]int, len(tasks))
wg := sync.WaitGroup{}
for i, task := range tasks {
wg.Add(1)
go func(i int, task func() int) {
defer wg.Done()
results[i] = task()
}(i, task)
}
wg.Wait()
return results
}
func main() {
tasks := []func() int{
func() int {
time.Sleep(2 * time.Second)
return 1
},
func() int {
time.Sleep(1 * time.Second)
return 2
},
func() int {
time.Sleep(3 * time.Second)
return 3
},
}
results := ConcurrentTask(tasks)
fmt.Println(results) // 输出:[1 2 3]
}
//OUTPUT
//[1 2 3]
上例中,for循环为每个任务创建一个匿名函数。这些匿名函数使用闭包来捕获循环变量i
和任务函数task
。在每个匿名函数内部,我们调用任务函数,并将结果存储在相应的位置。
总结
本文主要学习记录Go语言中一个强大、极具表现力的特性:闭包。包括闭包的基础概念,和匿名函数的区别,以及一些常见的编程场景。闭包因为其灵活性,可塑性强,在开源库里广泛运用,对提升代码的可维护性、拓展性上有比较大的帮助。
参考
- a Tour of Go 闭包介绍 go.dev/tour/morety...
- Blog: Go语言中的闭包:封装数据与功能的强大工具mp.weixin.qq.com/s/qTpS2USZc...
- Blog: Go 语言闭包详解juejin.cn/post/684490...