go 多重循环中对结构体切片append 最后结果和预期不一致

问题遇到的现象和发生背景

go 多重循环中对结构体切片append 最后结果和预期不一致

问题相关代码,请勿粘贴截图
package main

import (
    "fmt"
    _ "github.com/mattn/go-oci8"
    "strconv"
    _ "test-app/internal/packed"
)

type A struct {
    aa int
    ab string
    cc []B
}
type B struct {
    ba int
    bb string
}
func main() {
    var q1 []A

    q2:=make([]A,0)
    q1=[]A{{aa:1,ab:"1",cc:[]B{{ba: 1},{ba: 2},{ba: 3}}},{aa:2,ab:"2",cc:[]B{{ba: 1},{ba: 2},{ba: 3}}}}
    number:=0
    for _, q1v := range q1 {
        x:=[]string{"11","22"}
        for _, iv2 := range x {
            ls:=q1v
            for i, _ := range ls.cc {
                number+=1
                sj:=iv2 +"----"+strconv.Itoa(number)
                ls.cc[i].bb=sj
            }
            fmt.Printf("加入临时值:%+v\n",ls)
            q2=append(q2,ls)
        }

    }
    for _, a := range q2 {
        fmt.Printf("输出:%+v\n",a)
    }
    //fmt.Printf("s%+v",q2)
}

运行结果及报错内容

img

可以看出最后输出的和加入临时值的时候并不一样

我的解答思路和尝试过的方法

没找到解决方案

我想要达到的结果

最后输出的结果和 加入临时值一致

哈喽,我来告诉你原具体情况,
1,append到q2中的每次的数据和最后打出来的q2确实是不一样的,你没看错
2,最外层循环是两次,每隔两次q2的值会被改一下,改的新值就是最内层循环ls.cc[i].bb = sj的值,i的值每次分别为0,1,2也就是取的i=2时候的值
3,每次执行到最后一次ls.cc[i].bb = sj的时候,看起来是改的ls,实际上连位置为i的q2的值也一块改了;并且q1v和q2是同步修改的,改ls时q1v跟着改;
4,实际上最终q2里的值都是q1v的每次最新值,那么到这里原因就浮现了,第二层循环每进行一次都把最新的q1v赋给了ls变量,换句话说,代码里的ls实际上意义不大,有和没有效果一样(q2直接拼接q1v的值也是这个结果)
===>q2每次拼接的对象地址和ls在最内层循环最后一次修改的值都在同一个地址,因此q2跟着改了

最后,可以把number := 0 调整到第二和第三层循环之间就看得比较清楚了
如果想要每次加的值和最终q2元素相同,可以这么改:
func main() {
q2 := make([]A, 0)
q1 := []A{
{aa: 1, ab: "1", cc: []B{{ba: 1}, {ba: 2}, {ba: 3}}},
{aa: 2, ab: "2", cc: []B{{ba: 1}, {ba: 2}, {ba: 3}}},
}
for _, q1v := range q1 { // 2次
for _, iv2 := range []string{"11", "22"} { // 2次
ls,number := &A{aa: q1v.aa, ab: q1v.ab, cc: make([]B, 3)},0
for i := range q1v.cc { // 3次
number += 1
sj := iv2 + "----" + strconv.Itoa(number)
ls.cc[i].ba = q1v.cc[i].ba
ls.cc[i].bb = sj
}
fmt.Printf("加入临时值:%+v\n", ls)
q2 = append(q2, *ls)
}
}
for _, a := range q2 {
fmt.Printf("输出:%+v\n", &a)
}
//fmt.Printf("s%+v",q2)
}

如果解决了疑问,欢迎采纳哦!

原因:临时变量在遍历过程中,是重复利用的。遍历结构体数据切片并修改字段后,存入到结构体指针切片中,在此过程中,临时变量v的内存地址始终不变,存入的均为临时变量的地址。等循环结束后,由于go使用的是引用计数算法,因此v并不会被垃圾回收,存入的所有结构体数据均为最后一次遍历的数据。
更改:

  1. 原切片改用指针类型;
  2. 结果切片改为结构体;
  3. 直接操作原切片;

    for _, q1v := range q1 {

        x:=[]string{"11","22"}

        for _, iv2 := range x {

            ls:=q1v

两次循环时ls都赋值了q1v,里面的切片类似指针,不是重新拷贝一份,而是同一份。所以操作后结果都为最后的赋值结果。

共享底层数组的切片
需要注意的是:现在两个切片 myNum 和 newNum 共享同一个底层数组。如果一个切片修改了该底层数组的共享
部分,另一个切片也能感知到

复制切片使用copy方法。

一定要记住,切片的底层数组是共享的,来自同一个数组的多个不同切片,修改其中任何一个都会对其他切片造成影响。