go语言坑之 for ... range 与指针
// 项目问题,得到的result中mix相同
// mix *MixMgmtComponents
//var result []*db.MixMgmtComponents
//var componentsSlice []db.MgmtComponents
for _, c := range componentsSlice {
mix, err := db.Model.InsertGetMixMgmtComponents(c)
if err != nil {
msg := fmt.Sprint("查询mix组件失败:", err.Error())
log.Logger.Error(msg)
}
result = append(result, mix)
}
问题简化: 定义一个map[int]int对象,分别赋值给map[int]*int 对象,并分别打印map[int]*int中的值 期待结果:map[int]*int中每个值与map[int]int相同。
package main
import "fmt"
func main() {
slice := []int{0, 1, 2, 3}
myMap := make(map[int]*int)
for index, value := range slice {
myMap[index] = &value
}
fmt.Println("=====new map=====")
prtMap(myMap)
}
func prtMap(myMap map[int]*int) {
for key, value := range myMap {
fmt.Printf("map[%v]=%v\n", key, *value)
}
}
预期的输出:
=====new map=====
map[0]=0
map[1]=1
map[2]=2
map[3]=3
运行程序输出如下:
=====new map=====
map[3]=3
map[0]=3
map[1]=3
map[2]=3
问题分析: 因为for range创建了每个元素的副本,而不是直接返回每个元素的引用,如果使用该值变量的地址作为指向每个元素的指针,就会导致错误, 在迭代时,返回的变量是一个迭代过程中根据切片依次赋值的新变量,所以值的地址总是相同的,导致结果不如预期。
for i, v := range sliceObj {
...
}
var i int
var v sliceObjType
// 遍历sliceObj并依次赋值进入{}
源码分析(go gcc) for .. range 是对 for INIT ; COND ; POST {} 结构的语法糖
// Arrange to do a loop appropriate for the type. We will produce
// for INIT ; COND ; POST {
// ITER_INIT
// INDEX = INDEX_TEMP
// VALUE = VALUE_TEMP // If there is a value
// original statements
// }
if (range_type->is_slice_type())
this->lower_range_slice(...);
else if (range_type->array_type() != NULL)
this->lower_range_array(...);
else if (range_type->is_string_type())
this->lower_range_string(...);
else if (range_type->map_type() != NULL)
this->lower_range_map(...);
else if (range_type->channel_type() != NULL)
this->lower_range_channel(...;
else
go_unreachable();
void For_range_statement::lower_range_slice(...)
{
// The loop we generate:
// for_temp := range
// len_temp := len(for_temp)
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// value_temp = for_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }
//
// Using for_temp means that we don't need to check bounds when
// fetching range_temp[index_temp].
// Set *PINIT to
// range_temp := range
// var len_temp int
// len_temp = len(range_temp)
// index_temp = 0
....
}
修正:
for index, value := range slice {
num := value
myMap[index] = &num
}
项目修正:
- db.Model.InsertGetMixMgmtComponents(c) 函数传递对象,而不是一个指针
- 用一个对象接受指针的值,拷贝传递给函数作为实参。
坑的延伸
for i, v := range sliceObj {
go func(){
i,v....
}()
}
正确用法: range中的对象,作为函数参数传递,不受闭包影响
for i, v := range sliceObj {
go func(i, v){
i,v....
}()
}