切片修改问题

问题描述

1
2
3
4
5
6
7
8
9
func test(a [][]int) {
a = append(a, []int{1, 2, 3})
}
func main() {
a := [][]int{}
test(a)
fmt.Println(a) //0
}

执行完后a居然是空?切片内部不是存放了指针吗?

问题解决

这个问题其实是append导致的,append时,如果超过了a的最大容量,那么会重新分配空间并返回,这种情况下对原值指向的位置没有做修改
a默认容量为0,append追加后重新分配空间返回,test函数外打印的是原地址空间内容,自然没有改变

衍生问题
那是不是说切片提前分配了足够的空间(未使用)以避免分配空间就可以外层也可以看到函数内的修改?我们试一下

1
2
3
4
5
6
7
8
9
10
func test(a []int) {
a = append(a, 1)
fmt.Println(a)
}
func main(){
a := make([]int, 1, 10) //长度1 最大容量为10
test(a)
fmt.Println(a) //0
}

what? 还是0?啥情况?
这另一个原因导致,那就是slice的限制,切片输出时只会输出切片长度的内容,实际上原地址空间内容已经更改了,我们尝试调整下a的长度再看

1
2
a = a[:len(a)+1]
fmt.Print(a) //0,1

看,完全符合我们的解释

衍生问题2
在计算全排列时使用多维切片,再次碰到坑

  • 对多维切片append值时,应该先对值复制再append
    比如下例中如果直接*res = append(*res,t),由于t是引用类型,在其后操作中对t的修改同样会影响到已保存到res中的值,所以必须深复制,在执行追加操作

深复制,源切片和目的切片彼此拥有独立的底层数组空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func main() {
res := permute([]int{5, 4, 6, 3})
fmt.Println(res)
}
func permute(nums []int) [][]int {
res, t, m := [][]int{}, []int{}, make(map[int]bool)
permuteHelper(&res, nums, t, m)
return res
}
func permuteHelper(res *[][]int, nums []int, t []int, m map[int]bool) {
if len(t) == len(nums) {
//重点 slice是引用类型 不可以直接赋值 否则下次修改会影响之前res中以保存的结果 这里一定要拷贝
temp := make([]int, len(t))
copy(temp, t)
*res = append(*res, temp)
return
} else {
for i := 0; i < len(nums); i++ {
if _, ok := m[i]; !ok {
m[i] = true
t = append(t, nums[i])
permuteHelper(res, nums, t, m)
t = t[:len(t)-1]
delete(m, i)
}
}
}
}

衍生问题3
如果希望切片函数传递时,一直操作的是同一个该怎么做呢?
答案是利用传统方式—指针,例如上例中permuteHelper的第一个形参使用的是*[][]int保证一直是对同一个res操作
那为什么上例中的形参3t可以不用指针呢?正常来讲是要用指针的,不过在该题中用指针和不用指针结果一样