最近在准备面试,看了一些Go关于slice的问题,从Go的slice函数传参到slice的扩容等问题,在这里总结一下
场景预设1
我们需要设计一个change函数,这个函数需要传入一个int类型的切片,然后将该切片的所有元素的值修改为0
我们也许会这样设计
func main() {
arr := []int{1, 2, 3}
change(arr)
fmt.Println(arr)
}
func change(arr []int) {
for i := 0; i < len(arr); i++ {
arr[i] = 0
}
}
运行结果
可以看到,在这种情况下是可以在函数里面直接修改传入的切片的
Go的函数传参
为什么呢?
这就涉及Go的函数传参方式了,众所周知,Go的函数传参,只有值传递这一种,但对于slice切片来说,其实在函数传参的时候,传递的是指针
场景预设2
此时我们的设计需求发生了变换,在原来的change函数,我们希望切片所有元素值修改成0后,在后面添上一个1
此时代码只需增加一个append
func main() {
arr := []int{1, 2, 3}
change(arr)
fmt.Println(arr)
}
func change(arr []int) {
for i := 0; i < len(arr); i++ {
arr[i] = 0
}
arr = append(arr, 1)
}
运行结果如下
可以看到原数组没有后面没有添加1
为什么?
我们尝试打印一下arr在不同阶段的地址
func main() {
arr := []int{1, 2, 3}
fmt.Printf("执行change前 arr地址%p\n", arr)
change(arr)
fmt.Println(arr)
}
func change(arr []int) {
for i := 0; i < len(arr); i++ {
arr[i] = 0
}
fmt.Printf("执行append前 arr地址%p\n", arr)
arr = append(arr, 1)
fmt.Printf("执行append后 arr地址%p\n", arr)
}
可以看到append后arr地址发生改变,但是返回main函数打印arr地址又变回来了
slice的扩容机制
原因就在于append在扩容的时候,如果空间不够,会将扩容后的数组,放到新的空间里面,但是我们在main函数使用的还是原来那片地址空间的数组,所以不会发生改变
所以,是不是我们提前使用make就可以避免了?
func main() {
arr := make([]int, 0, 10)
arr = append(arr, []int{1, 2, 3}...)
fmt.Printf("执行change前 arr地址%p\n", arr)
change(arr)
fmt.Printf("arr长度%d 容量%d\n", len(arr), cap(arr))
fmt.Printf("执行change后 arr地址%p\n", arr)
fmt.Println(arr)
}
func change(arr []int) {
for i := 0; i < len(arr); i++ {
arr[i] = 0
}
fmt.Printf("执行append前 arr地址%p\n", arr)
arr = append(arr, 1)
fmt.Printf("arr长度%d 容量%d\n", len(arr), cap(arr))
fmt.Printf("执行append后 arr地址%p\n", arr)
}
执行结果
可以看到arr地址没有发生改变,但是仍然没有成功扩容
这里问题可以参考Go Slice 扩容的这些坑你踩过吗?
Slice的底层结构
这个问题我们需要先看下slice的底层结构是怎样的
//runtime/slice.go
type slice struct {
array unsafe.Pointer
len int
cap int
}
可以看到实际是一个结构体,结合前面函数传参是值传递的情况,arr相关的结构体也是值传递,也就是实际main函数的slice切片和change函数的slice切片所使用的储存切片的结构体是不同一个的,但是他们的指针是指向同一个数组空间