golang把slice append进二维slice后,为何slice会自动改变

/**
PAC:Permutation and combination 的缩写
递归的方式获取所有排列组合

plans 已有的排列组合,递归用的,外部调用时传入一个空的二维数组即可 [][]int{}
num 有多少个数参与排列组合,如传入3,则参与的数是0,1,2
step 每个组合有多少个数
*/
func getPAC(plans [][]int,num int,step int)[][]int{
    newPlans := [][]int{}
    if len(plans)>0 {
        fmt.Println("还有",step-1,"次:")
        for _,v := range plans{
            for i:=0;i<num;i++ {
                group := append(v,i)
                fmt.Println(group)
                newPlans = append(newPlans,group)
            }
        }
        if step==1 {
            fmt.Println("最终结果:",newPlans)
        }
    }else{
        for i:=0;i<num;i++ {
            newPlans = append(newPlans,[]int{i})
        }
    }
    if step>1 {
        newPlans = getPAC(newPlans,num,step-1)
    }

    return newPlans
}

使用方法:getPAC([][]int{},3,4)

我写了一个用递归的方式生成所有排列组合的函数

step<=3时是正常的,但是如果step>3,最终结果会出现:[[0 0 0 2] [0 0 0 2] [0 0 0 2] [0 0 1 2] [0 0 1 2] [0 0 1 2] [0 0 2 2] [0 0 2 2] [0 0 2 2] [0 1 0 2] [0 1 0 2]这种情况,即每个子数组的最后一项都是2,正常来说最后一项是 012 这3个轮流出现。

一开始我以为是二维slice的子slice生成的时候出了问题,所以我在把子slice加入二维slice前打印了一下,发现是没问题的,但是把slice加入二维slice之后就变了个样

请问这是为什么?因为刚接触go,对go的一些特性还不了解


题外话,如果要实现与上面这个相同且性能更高的方法还是有的
比如逻辑不变把 [][]int 换成 []string
比如把step看成进制,如果是2则2进制、3则3进制,获取这个进制指定位数的最大值+1,然后遍历这个值的次数,把所有的值都加进数组里,也是一个方法,就是抽象了点

但此题目我想了解golang的二维slice到底有什么问题,因为这个问题感觉太奇怪了。

阅读 7.7k
2 个回答

知道原因了,是go的slice的问题,参考个问题:
https://www.zhihu.com/questio...

根据上面的问答,得出一个理解和一个结论

理解:
slice或者数组,其子项的内存地址必定是连续的。slice的长度和容量是两个概念,长度是slice中有多少个值,容量是slice中最多能存多少个值。当slice要增加子项时,总长度不能超过容量。

结论:
append会判断原slice容量够不够,不够则创建一个容量为原来的两倍的新slice,并给新slice添加元素,如果够则直接改变原slice未使用的第一个内存地址的值为新元素。

由结论猜想出的一种bug:
有一个slice:a,容量为4,长度为3。把a用作append的第一个参数,返回值赋予给一些新slice,则这些新slice每个子项的内存地址都是一一相等的,意味着改变了任何一个slice的任意一个值,其它slice对应的值也会跟着变

show you the code


a := make([]int,3,4)
a[0] = 0
a[1] = 1
a[2] = 2
b := append(a,10)
c := append(a,100)
c[0] = 101

fmt.Println(&a[0],&b[0],&c[0]) //4 4 0xc0423eb500 0xc0423eb500 0xc0423eb500
fmt.Println(a[0],b[0],c[0]) //101 101 101
fmt.Println(b[3],c[3]) //100 100
新手上路,请多包涵

两个原因:

  1. slice作为参数传递,是值传递,但是底层数组地址不变的话,最后的结果都会是一样的
  2. 不一样的原因是append扩容可能会导致底层数组发生变化,所以看到结果又不一样
package main

import "fmt"

func main() {
    var result [][]int
    track := []int{1}
    result = append(result, track)
    fmt.Println(track, result)

    track[0] = 9
    result = append(result, track)
    fmt.Println(track, result)

    track = append(track, 2)
    result = append(result, track)
    fmt.Println(track, result)
}

结果:

[1] [[1]]
[9] [[9] [9]]
[9 2] [[9] [9] [9 2]]

第二次append之前,track的底层数组没有变化,所以变成了9,result里也变成了9
第三次append之前,track扩容导致底层数组发生变化,创建了一个新的,result的第三个元素的底层数组就是新的那个,前两个还是旧的就还是9
直接打印track+%p可以看到地址发生变化

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题