
在 go 中,for 循环中创建的闭包若直接引用循环变量,会导致所有闭包共享同一变量实例,从而输出意外的重复值;正确做法是在每次迭代中通过短变量声明(s := s)显式创建新变量副本。
这是 Go 开发中一个高频且易被忽视的经典陷阱:循环变量在闭包中被意外复用。根本原因在于:Go 的 for 循环体复用同一个变量内存地址——每次迭代仅更新其值,而非创建新变量。因此,所有匿名函数捕获的都是对同一个 s 变量的引用,当循环结束时,s 的最终值(即 "world")成为所有闭包实际读取的内容。
✅ 正确解法:在循环体内使用 短变量声明 创建独立副本:
package main
import "log"
var functions []func()
func main() {
for _, s := range [...]string{"goodbye", "cruel", "world"} {
s := s // ✅ 关键:为本次迭代创建新的局部变量 s
functions = append(functions, func() {
log.Println(s) // 此处 s 绑定的是本轮迭代的独立副本
})
}
for _, f := range functions {
f()
}
}? 输出结果将符合预期:
2009/11/10 23:00:00 goodbye 2009/11/10 23:00:00 cruel 2009/11/10 23:00:00 world
⚠️ 注意事项:
- 不要尝试用 new(func()) 或 make() 创建函数实例——Go 中函数类型不可分配内存,func() 是不可寻址的类型,不支持 new 或 make;
- 该问题不仅影响普通闭包,也广泛存在于 go func() { ... }() 启动的 goroutine 中(参见 Go FAQ: Closures and Goroutines);
- 替代写法(更显式但稍冗余):使用带参数的立即执行函数,如 func(s string) { ... }(s),但短变量声明更简洁、更符合 Go 惯例。
? 总结:闭包捕获的是变量的绑定(binding),而非值的快照;要捕获当前值,必须捕获一个生命周期独立、值不变的新变量。养成在循环中创建闭包前主动“复制”循环变量的习惯,是写出健壮 Go 代码的重要实践。










