直接用例子说话
例1:
func main() {
s := make([]int, 1, 3) // 创建一个长度为1,容量为3的切片
fmt.Printf("before: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
modifySlice1(s)
fmt.Printf("after: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
}
func modifySlice1(s []int) {
s = append(s, 2)
s[0] = 1
fmt.Printf("modify: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
}
运行结果:
before: slice addr 0xc000004480 point 0xc00000a480, val [0], len 1, cap 3
modify: slice addr 0xc000004500 point 0xc00000a480, val [1 2], len 2, cap 3
after: slice addr 0xc000004480 point 0xc00000a480, val [1], len 1, cap 3
- 通过参数传过去的slice地址不一样,说明slice进行了拷贝(见:addr)
- 函数里面修改了slice后原先的slice内容也改变了说明他们的底层指向的是同一份数据(见:point)
例2,与上面代码的区别是append了三个元素
func main() {
s := make([]int, 1, 3) // 创建一个长度为1,容量为3的切片
fmt.Printf("before: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
modifySlice2(s)
fmt.Printf("after: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
}
func modifySlice2(s []int) {
s = append(s, 2, 3, 4)
s[0] = 1
fmt.Printf("modify: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
}
运行结果:
before: slice addr 0xc000054420 point 0xc00005c140, val [0], len 1, cap 3
modify: slice addr 0xc0000544a0 point 0xc000088030, val [1 2 3 4], len 4, cap 6
after: slice addr 0xc000054420 point 0xc00005c140, val [0], len 1, cap 3
- 函数里面slice底层point指向的地址变了,是由于append的元素个数超出了原先的cap,底层进行了内存重分配以及数据拷贝
- 函数里面的修改并没有影响到外面的slice
通过上面两个例子就可以看出这样使用slice容易掉入陷阱,函数外面并不知道slice被修改了。
解决方法,遵守这两个规则:
- 无返回参数的函数我们不应该去修改slice,只进行读取
- 如果要修改slice那就应该通过返回值返回
所以正确的修改slice的方法是:
func main() {
s := make([]int, 1, 3) // 创建一个长度为1,容量为3的切片
fmt.Printf("before: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
s = modifySlice3(s)
fmt.Printf("after3: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
s = modifySlice4(s)
fmt.Printf("after4: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
}
func modifySlice3(s []int) []int {
s = append(s, 2)
s[0] = 1
fmt.Printf("modify3: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
return s
}
func modifySlice4(s []int) []int {
s = append(s, 3, 4, 5, 6)
s[0] = 1
fmt.Printf("modify4: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
return s
}
输出结果:
before: slice addr 0xc00004e420 point 0xc000054140, val [0], len 1, cap 3
modify3: slice addr 0xc00004e4a0 point 0xc000054140, val [1 2], len 2, cap 3
after3: slice addr 0xc00004e420 point 0xc000054140, val [1 2], len 2, cap 3
modify4: slice addr 0xc00004e540 point 0xc000080030, val [1 2 3 4 5 6], len 6, cap 6
after4: slice addr 0xc00004e420 point 0xc000080030, val [1 2 3 4 5 6], len 6, cap 6
这样不管底层数据有没有产生拷贝,表现在外的值都是一致的。
结论:
- 函数返回值没有返回slice时,应该只读
- 函数修改了slice时,应该通过返回值返回slice
- 如果在函数里面确实要修改slice而又不想改变外面的值时,您应该深拷贝一份slice再进行修改。