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++简洁许多