匿名函数与闭包
本教程共 80 篇 · 第 24 篇 · 更新于 2026-07-27 · 约 8 分钟阅读
24. 匿名函数与闭包
本节目标:掌握匿名函数的写法,理解闭包的捕获机制,学会避免闭包的常见陷阱。
匿名函数
匿名函数就是没有名字的函数。Go 里叫函数字面量(Function literal)。
基本写法
func(x, y int) int {
return x + y
}
这就是一个匿名函数。它没有名字,但定义了参数和返回值。
立即执行
result := func(x, y int) int {
return x + y
}(3, 4)
fmt.Println(result) // 7
在函数后面直接加 (3, 4) 就立即调用。这种模式叫 IIFE(Immediately Invoked Function Expression)。
赋值给变量
add := func(x, y int) int {
return x + y
}
fmt.Println(add(3, 4)) // 7
赋值给变量后,就可以用变量名来调用了。这和用 func 声明函数的效果类似,但写法更灵活。
作为参数
func apply(f func(int, int) int, a, b int) int {
return f(a, b)
}
func main() {
result := apply(func(x, y int) int {
return x * y
}, 3, 4)
fmt.Println(result) // 12
}
直接在调用时定义一个匿名函数传进去。
作为返回值
func multiplier(factor int) func(int) int {
return func(x int) int {
return x * factor
}
}
func main() {
double := multiplier(2)
triple := multiplier(3)
fmt.Println(double(5)) // 10
fmt.Println(triple(5)) // 15
}
multiplier 返回一个函数,这个函数「记住」了 factor 的值。这就是闭包。
闭包
闭包(Closure)是匿名函数加上它引用的外部变量。
闭包捕获外部变量
func main() {
count := 0
increment := func() {
count++ // 捕获了外部的 count
}
increment()
increment()
increment()
fmt.Println(count) // 3
}
increment 函数引用了外部的 count 变量。每次调用 increment,count 都会加 1。这就是闭包:函数「关闭」了它引用的外部变量,形成了封闭的作用域。
闭包捕获的是引用
闭包捕获的是变量的引用,不是值的副本:
func main() {
x := 10
f := func() {
fmt.Println(x)
}
x = 20
f() // 20,不是 10
}
f 捕获的是 x 的引用。当 x 变成 20 后,f() 输出的也是 20。
闭包实现计数器
func makeCounter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
counter := makeCounter()
fmt.Println(counter()) // 1
fmt.Println(counter()) // 2
fmt.Println(counter()) // 3
}
count 变量在 makeCounter 返回后,外部无法直接访问,但闭包 counter 仍然能操作它。这是一种封装数据的方式。
多个闭包共享变量
func makePair() (func() int, func(int)) {
var value int
getter := func() int {
return value
}
setter := func(v int) {
value = v
}
return getter, setter
}
func main() {
get, set := makePair()
fmt.Println(get()) // 0
set(42)
fmt.Println(get()) // 42
}
getter 和 setter 共享同一个 value 变量。
闭包陷阱
Go 1.22 之前的循环变量陷阱
在 Go 1.22 之前,循环变量在整个循环中只有一个,闭包捕获的是同一个变量:
// Go 1.21 的行为(有 bug)
funcs := []func(){}
for i := 0; i < 3; i++ {
funcs = append(funcs, func() {
fmt.Println(i)
})
}
for _, f := range funcs {
f()
}
// 输出:3 3 3(全部是循环结束后的值)
所有闭包捕获的是同一个 i,循环结束后 i 是 3,所以全部输出 3。
旧版的解决办法:
for i := 0; i < 3; i++ {
i := i // 创建局部副本
funcs = append(funcs, func() {
fmt.Println(i)
})
}
Go 1.22+ 的修复
Go 1.22 开始,每次迭代创建新的变量,闭包捕获的是各自的 i:
// Go 1.22+ 的行为(已修复)
funcs := []func(){}
for i := 0; i < 3; i++ {
funcs = append(funcs, func() {
fmt.Println(i)
})
}
for _, f := range funcs {
f()
}
// 输出:0 1 2
Note这个修复在上一章「循环变量语义」里详细讲过。Go 1.22+ 你不需要再担心这个陷阱了。
goroutine 中的闭包
// Go 1.22+ 正确行为
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i)
}()
}
// 可能输出:0 1 2(顺序不确定)
Go 1.22 之前需要传参:
for i := 0; i < 3; i++ {
go func(i int) {
fmt.Println(i)
}(i)
}
闭包的实际应用
回调函数
func fetchData(url string, onSuccess func(data string), onError func(err error)) {
// 模拟异步操作
if url == "" {
onError(errors.New("URL 不能为空"))
return
}
onSuccess("来自 " + url + " 的数据")
}
func main() {
fetchData("https://example.com",
func(data string) {
fmt.Println("成功:", data)
},
func(err error) {
fmt.Println("失败:", err)
},
)
}
延迟执行
func main() {
msg := "Hello"
// defer 注册一个匿名函数
defer func() {
fmt.Println(msg) // 在 main 退出时执行
}()
msg = "World"
fmt.Println("函数执行中")
}
// 输出:
// 函数执行中
// World
Tip
defer的参数在注册时就求值,但闭包捕获的变量在执行时才读取。所以msg输出的是修改后的 “World”。defer 的细节下一节讲。
中间件模式
func logger(handler func(string)) func(string) {
return func(msg string) {
fmt.Println("[LOG] 开始处理")
handler(msg)
fmt.Println("[LOG] 处理完成")
}
}
func main() {
sayHello := func(name string) {
fmt.Println("Hello,", name)
}
loggedHello := logger(sayHello)
loggedHello("Go")
}
// 输出:
// [LOG] 开始处理
// Hello, Go
// [LOG] 处理完成
函数工厂
func makeAdder(base int) func(int) int {
return func(x int) int {
return x + base
}
}
func main() {
addTen := makeAdder(10)
addTwenty := makeAdder(20)
fmt.Println(addTen(5)) // 15
fmt.Println(addTwenty(5)) // 25
}
每个闭包都有自己的 base,互不影响。
闭包和内存
闭包引用的外部变量不会被垃圾回收,只要闭包还在使用:
func makeCounter() func() int {
count := 0 // 这个变量不会被 GC
return func() int {
count++
return count
}
}
只要返回的闭包还在被引用,count 就不会被回收。这是闭包的正常行为,但要注意不要意外持有大量数据。
常见问题
闭包修改外部变量
func main() {
x := 10
f := func() {
x = 20 // 修改了外部的 x
}
f()
fmt.Println(x) // 20
}
闭包可以修改捕获的变量,不只是读取。
闭包返回值
func wrapper() func() int {
x := 0
return func() int {
x++
return x
}
}
每次调用返回的函数,x 会累加。不同调用得到不同的 wrapper() 返回值,各自独立:
c1 := wrapper()
c2 := wrapper()
c1() // 1
c1() // 2
c2() // 1(独立的计数器)
c1() // 3
闭包 vs 全局变量
闭包比全局变量的好处是封装。外部无法直接访问闭包捕获的变量,只能通过闭包提供的接口操作:
// 闭包方式:count 被封装,只能通过 increment 修改
counter := makeCounter()
// 全局变量方式:count 可以被任何代码修改,不安全
var count int
func increment() { count++ }
学完这节你能做什么
- 写匿名函数并立即执行或赋值给变量
- 理解闭包捕获外部变量的机制
- 用闭包实现计数器、函数工厂
- 避免 Go 1.22 之前的循环变量闭包陷阱
- 用闭包实现回调、中间件等模式
下一节讲 defer 语句。