Go踩的坑
今天在用go做一道回溯算法题
func subsetsWithDup(nums []int) [][]int {
sort.Ints(nums)
var res [][]int = make([][]int, 0)
path := make([]int, 0)
var dfs func(bool, int)
dfs = func(preNum bool, pos int) {
if pos == len(nums) {
//fmt.Println(path)
//fmt.Println(res)
res = append(res, path)
//fmt.Println(res)
//fmt.Println("--------------------------------------------------")
return
}
dfs(false, pos+1) //不加入当前的数字
if preNum == false && pos > 0 && nums[pos-1] == nums[pos] { //如果前面没放,当前数字与前面相同,那么不用经过当前放的分支,因为和前面放当前不放相同
return
}
path = append(path, nums[pos])
dfs(true, pos+1) //加入当前位置的数
path = path[:len(path)-1]
}
dfs(false, 0)
return res
}
发现在回溯改变path
切片时,res
中上次放入的path
结果也会改变,这与Go的函数参数传递方式有关。
Go的传值
在Go中,所有的传参都是传值,也就是都是拷贝一份传递的参数,那么为什么已经拷贝传递进res
中的path
会改变呢?这与Go的切片数据结构有关。
基础类型
Go的基本类型:byte
、int
、bool
、string
均是传值引用
数组
Go的数组也是传值引用
func main() {
var array = [3]int{0, 1, 2}
var array2 = array
slice2[2] = 5
fmt.Println(array, array2)
}
// 输出结果:
// [0 1 2] [0 1 5]
也可以使用指针来实现传引用:
func main() {
var array = [3]int{0, 1, 2}
var array2 = &array
array2[2] = 5
fmt.Println(array, *array2)
}
// 输出结果:
// [0 1 5] [0 1 5]
切片
先要对切片数据结构有了解
示意图:
一个切片是一个数组片段的描述。它包含了指向数组的指针,片段的长度, 和容量(片段的最大长度)。
下面是slice在源码中的结构
// runtime/slice.go
type slice struct {
array unsafe.Pointer // 数组指针
len int // 长度
cap int // 容量
}
虽然切片传递还是传值,会拷贝一份切片,但底层数组不会拷贝,拷贝出的新切片与老切片指向同一个底层数组,自然修改老切片会影响新切片中的值。
所以对于切片来说,传值差不多就是传引用了。要想真正的拷贝一份可以这样:
res = append(res, append([]int{},path...))
在这下面这句话中
append([]int{},path...)
[]int{}
是新建一个空切片,也可以[]int(nil)
,因为要用的时append
的返回值,不需要给这个新的空切片起名
那么新建的空切片底层数组是空,与老切片不公用一个底层数组,向其中添加元素时会进行扩容
...
是一种语法糖,在slice中用法是将path打散再一个个放入新切片中
append
返回一个包含原切片所有元素加上新添加元素的切片。
此时这个切片就可以添加到res
中而不受path
影响了