Golang Slice踩坑(一)


Go语言中Slice有一个常见的坑,就是数组的切片是否会改变原数组,网上解释的已经非常多了,今天我们来说一下另一个与Slice有关的问题

问题是如何发现的

今天在刷LeetCode题目,全排列,一道回溯法的题目,先用C++写了,没什么问题,然后用golang重写,问题就来了

全部代码如下:

func permute(nums []int) [][]int {
    res := [][]int{}
    var backtrack func(int)
    backtrack = func(idx int) {
        if idx == len(nums) {
	    res = append(res, nums)   
	    return
        }

        for i := idx; i < len(nums); i++ {
	    nums[i], nums[idx] = nums[idx], nums[i]
	    backtrack(idx + 1)
	    nums[i], nums[idx] = nums[idx], nums[i]
        }
    }

    backtrack(0)
    return res
}

这段代码并不和预期的一样输出正确的结果,也就是nums的全排列,而是:

[[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]

关键点在哪

关键点在于:

res = append(res, nums)

多次将切片nums改变并append到二位切片res中,结果并不与预期一样,在C++中,res中将存放nums每次的值,而golang,我们将res打印输出一下可以发现:

[[1 2 3]]
[[1 3 2] [1 3 2]]
[[2 1 3] [2 1 3] [2 1 3]]
[[2 3 1] [2 3 1] [2 3 1] [2 3 1]]
[[3 2 1] [3 2 1] [3 2 1] [3 2 1] [3 2 1]]
[[3 1 2] [3 1 2] [3 1 2] [3 1 2] [3 1 2] [3 1 2]]

没错,res中保存的并不是nums的值,而是nums的引用,对此,我们可以进行一次测试

测试

golang测试代码

matrix := [][]int{}
slice:= []int{1,2,3}

fmt.Println(&slice[0])       //输出slice的地址
matrix = append(matrix, slice)
fmt.Println(&matrix[0][0])    //输出matrix的地址

输出

0xc000014168
0xc000014168

对比组 C++测试代码

vector i{1,2,3,4,4};  
vector> matrix{};
matrix.push_back(i);
cout <<&matrix[0][0] << endl;
cout<<&i[0]<< endl;

输出

0x182530
0x2602490

可知golang二维切片里面确实存的是nums的引用,但是经过查阅,并没有找到解决方法
可知golang二维切片里面确实存的是nums的引用,也就是一个深拷贝和浅拷贝的问题

解决办法

既然默认是浅拷贝,那我们就要知道如何对Slice进行深拷贝,在golang中,可以使用copy函数进行深拷贝

copy( destSlice, srcSlice []T) int

因此在上面的问题中,我们可以创建一个临时对象,用于拷贝nums切片

if判断内可写为

new_nums := make([]int, len(nums))	//创建一个变量,注意要预分配大小,否则copy总是空		
copy(new_nums,nums)
res = append(res, new_nums)
return

或者更简洁一点,把nums拆开放到新的切片中,也可以达到目的:

res = append(res, append([]int(nil),nums...))

这样,二维切片res中的元素就不会再被nums改变,问题解决了。说实话,还是挺喜欢go的设计的,相比C++简洁许多