go闭包捕获变量本身而非值,导致for循环中闭包共享同一变量i而输出终值;正确做法是每次迭代用i := i遮蔽或传参go func(num int){}(i)。

Go 闭包捕获的是变量本身,不是声明时的值 —— 这是绝大多数闭包 bug 的根源。
for 循环里直接创建闭包,为什么输出全是最后一个值?
因为循环变量 i 在整个循环中只有一份,所有闭包都捕获了同一个地址。等循环结束、闭包执行时,i 已经是终值(比如 3),所以全部打印 3。
- 错误写法:
funcs[i] = func() { fmt.Println(i) }—— 捕获的是变量i,不是它的某次迭代值 - 正确做法:在每次迭代中用新变量遮蔽,
i := i,再让闭包捕获这个新变量 - 更安全的替代:把值作为参数传进闭包,
go func(num int) { fmt.Println(num) }(i) - 注意:Go 1.22+ 对
for range的迭代变量做了隐式复制优化,但仅限于range场景,for i := 0; i 仍需手动处理
闭包修改外部变量,为什么原变量真被改了?
因为 Go 闭包捕获的是变量的地址,不是副本。闭包内对 count 的读写,就是对原始栈/堆上那个内存位置的操作。
- 典型表现:
count++后,下次调用闭包会继续累加,说明状态被保留 - 本质是逃逸分析的结果:被闭包引用的变量会被编译器自动分配到堆上,生命周期延长
- 副作用明显:多个闭包共享同一变量时(如未隔离的循环变量),会产生竞态或意外覆盖
- 验证方式:打印
&count地址,闭包内外是一样的
怎么让闭包“记住”声明那一刻的值,而不是后续变化?
必须打断变量引用链,靠显式复制来切断共享。Go 不提供语法级的“按值捕获”开关,全靠开发者构造隔离作用域。
- 块级遮蔽最常用:
for i := 0; i - 函数参数传递更清晰:
makeHandler := func(val string) func() { return func() { fmt.Println(val) } }; h := makeHandler(x) - 避免硬编码字符串或数字——那不是捕获,只是常量嵌入,无法响应运行时输入
- 协程场景尤其危险:
go func() { ... }()中直接用循环变量,几乎必然出错
真正容易被忽略的点:闭包捕获行为发生在编译期,但效果暴露在运行时;你写的代码看起来没传参、没取地址,可内存布局和变量生命周期早已被编译器重排。别依赖直觉判断“这个值应该固定”,先看它是不是被多个闭包共用。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











