在 Go 中,匿名函数(函数字面量)能够创建闭包,即在其外部作用域完成后依然可以访问变量。这样的闭包如果需要正确运行,会在堆中分配内存(通过逃逸分析检测)。
示例:
func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } a := adder() printf("%d ", a(5)) // 5 printf("%d ", a(10)) // 15
特性:
这段代码会输出什么?
func main() { fs := []func(){} for i := 0; i < 3; i++ { fs = append(fs, func() { fmt.Println(i) }) } for _, f := range fs { f() } }
许多人会回答输出 0, 1, 2,但实际上结果是:
3
3
3
所有闭包都引用同一个变量 i;循环结束后它的值为 3。
正确做法:在循环体中捕获变量的副本:
for i := 0; i < 3; i++ { v := i // 新的变量 fs = append(fs, func() { fmt.Println(v) }) }
故事
在动态路由项目中,使用循环通过闭包创建了许多处理程序,每个处理程序都应该捕获自己的路径。结果所有处理程序打印了最后一个路径——没有在每个闭包中创建单独的变量。错误仅在与 HTTP API 集成时才被发现。
故事
在测试通过 goroutine 的并发访问时,闭包捕获了索引的引用,而不是副本。这导致了“随机”效果:数据没有写入自己的数组槽,而是写入了最后一个槽。
故事
在统计收集函数中,闭包修改了外部作用域的全局变量,尽管作者期望每个任务有独立的计数器。问题通过不恰当的重建总和被发现,总和总是公共的而不是私有的,尽管有局部逻辑。