记golang中结构体切片转指针踩坑

背景:

   开发阶段发现某数据结构切片中,字段值都相等,经排查后发现某次对结构体实例切片进行for循环遍历,以修改字段数值时,出现问题。

示例代码:

# forTest 模拟遍历循环结构体过程
func forTest() {

    // 定义函数内部结构体
    type forStruct struct {
        ID   int
        Name string
    }

    // 生成数据切片,并进行赋值操作
    list := make([]forStruct, 0)
    list = append(list, forStruct{1, "测试1"}, forStruct{2, "测试2"})
    // 存放遍历后修改的结果,其中result是结构体指针类型
    result := make([]*forStruct, 0)

    // 开始遍历数据,并打印出相关结果
    for i, v := range list {
        // 修改前数据
        fmt.Println(fmt.Sprintf("第%d行数据:%v", i, v))

        // 将ID值+10
        v.ID = v.ID + 10

        // 修改后数据
        fmt.Println(fmt.Sprintf("第%d行修改数据:%v", i, v))

        // 临时变量m的值
        fmt.Println(fmt.Sprintf("v临时变量的地址:%v", &v))

        // 切片原变量的值
        fmt.Println(fmt.Sprintf("切片原变量的地址:%v", &list[i]))

        fmt.Println("------------------------------------------")

        result = append(result, &v)
    }

    // 遍历返回的结果
    for i, r := range result {
        fmt.Println(fmt.Sprintf("第%d行数据为%v", i, r))
    }

}
输出结果:
第0行数据:{1 测试1}
第0行修改数据:{11 测试1}
v临时变量的地址:&{11 测试1}
切片原变量的地址:&{1 测试1}
------------------------------------------
第1行数据:{2 测试2}
第1行修改数据:{12 测试2}
v临时变量的地址:&{12 测试2}
切片原变量的地址:&{2 测试2}
------------------------------------------
第0行数据为&{12 测试2}
第1行数据为&{12 测试2}

  可以看到输出的最后两行都是相同的,这是什么原因呢?

原因

   golang中的参数传递都是值传递!
  在for循环遍历slice或者map时,生成了临时变量v,变量v在遍历过程中,是重复利用的。也就是说,无论是第几次遍历,始终都是将slice或者map中的数据复制到了v中,并通过操作v来实现内部变量。
  在上述示例代码中,处理逻辑是将遍历结构体数据切片并修改字段后,存入到结构体指针切片中,在此过程中,变量v的内存地址始终不变,存入的均为临时变量的地址。等循环结束后,由于golang使用的是引用计数算法,因此v并不会被垃圾回收,存入的所有结构体数据均为最后一次遍历的数据。

解决方式:

  1. 原切片改用指针类型:数据相同是由于存入的是临时变量地址,因此只需要将原本的切片存入类型从结构体统一成结构体指针即可。在操作v的时候,实际会操作切片底层的数据,操作完成后再次存储指针即可,或者直接使用原本的切片。
  2. 结果切片改为结构体: 与存入指针相对,也可以存为结构体。在遍历过程中,会生成一个新的结构体对象,并将这个结构实例存入结果中。不过建议大数据结构体还是使用指针,以减少内存复制成本。
  3. 直接操作原切片: 上述两种方式都是原现的切片类型统一,如果仍需要结构体转换为指针类型,则可不操作临时变量v,直接操作原变量,并将原变量赋值到结果集。

你可能感兴趣的:(记golang中结构体切片转指针踩坑)