Skip to content

Instantly share code, notes, and snippets.

@JesseYan
Last active August 6, 2018 08:57
Show Gist options
  • Save JesseYan/e8cca296bbcf18a9be2c6b89ed387027 to your computer and use it in GitHub Desktop.
Save JesseYan/e8cca296bbcf18a9be2c6b89ed387027 to your computer and use it in GitHub Desktop.
go语言坑之for range

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
}

项目修正:

  1. db.Model.InsertGetMixMgmtComponents(c) 函数传递对象,而不是一个指针
  2. 用一个对象接受指针的值,拷贝传递给函数作为实参。

坑的延伸

for i, v := range sliceObj {
	go func(){
		i,v....
	}()
}

正确用法: range中的对象,作为函数参数传递,不受闭包影响

for i, v := range sliceObj {
	go func(i, v){
		i,v....
	}()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment