
在 go 的 for-range 循环中,迭代变量是复用的(而非每次新建),若直接对其取地址并返回,将始终指向最后一次迭代的值——这是初学者易踩的“指针陷阱”,尤其对熟悉 python 等语言的开发者而言。
在 go 的 for-range 循环中,迭代变量是复用的(而非每次新建),若直接对其取地址并返回,将始终指向最后一次迭代的值——这是初学者易踩的“指针陷阱”,尤其对熟悉 python 等语言的开发者而言。
Go 的 for range 语句在遍历切片时,底层只声明一个迭代变量(如 record),并在每次迭代中将其值拷贝为当前元素。这意味着:该变量的内存地址在整个循环中保持不变,只是内容被不断覆盖。当你执行 &record 时,得到的是这个复用变量的地址,而非原切片中对应元素的地址。因此,无论你在哪次迭代中赋值 foundRecord = &record,最终返回的指针都指向同一个内存位置——而该位置在循环结束时保存的是最后一个元素(即 Record{3, "Third"})的值。
这并非 bug,而是 Go 明确设计的内存优化行为:避免每次迭代分配新变量,显著减少堆分配与 GC 压力。但这也要求开发者必须意识到——迭代变量不是“每个元素的独立别名”,而是一个被反复赋值的局部变量。
✅ 正确做法是:直接取切片元素的地址,即 &records[i]。因为切片底层数组的元素具有稳定内存地址(只要切片未发生扩容或被重新切片),其地址可安全返回:
func findRecord(id int) *Record {
for i, record := range records {
if record.Id == id {
return &records[i] // ✅ 安全:指向底层数组真实元素
}
// 其他逻辑可继续执行,无需 break
}
return nil
}
⚠️ 注意事项:
- 不要依赖 &record 获取结构体地址,除非你明确需要该变量的当前副本地址(且生命周期可控);
- 若需在循环中启动 goroutine 并捕获当前元素,同样需通过 records[i] 或显式拷贝(如 r := record; go func() { ... }())来避免闭包引用复用变量;
- 此行为在 Go 官方 FAQ 中有明确说明:Closures and Goroutines。
总结:Go 的 for-range 迭代变量复用是性能友好的设计,但要求开发者主动区分“值拷贝”与“内存地址”的语义。始终优先使用 &slice[i] 而非 &iterationVar 来获取可长期持有的元素指针——这是写出健壮、可维护 Go 代码的关键细节之一。











