在介绍slice函数参数传递之前,先介绍一下slice的结构
type slice struct {
array unsafe.Pointer
len int
cap int
}
这个应该周知了,也不必多解释,需要注意两个问题
1、如何初始化slice
我们知道初始化slice有几种方式,注意以下代码,参考链接
var foo []int // 1
foo := []int{} // 2
foo := make([]int, 0) // 3
那么这些方式有什么区别吗?
简单来说,1中foo == nil
,2和3中foo != nil
,并且2和3初始化了底层的array指针的地址,并且len == 0, capacity == 0
,2和3可以视为相同的。
2、如何打印slice的地址
注意以下代码
slice := []int{1}
fmt.Printf("%p", slice) // 1
fmt.Printf("%p", &slice) // 2
fmt.Printf("%p", &slice[0]) // 3
主要其实fmt.Printf(“%p”)对slice处理是有优化的,参考fmt
那么其实1和3结果是相同的。都是打印slice[0]的地址。但是2的结果有所不同,因为&slice其实是slice变量的地址,而不是其中array的地址。
搞清楚上面两个问题后,我们再来看将slice作为参数传递给函数,看如下代码,参考链接
package main
import "fmt"
func main() {
slice := []int{}
fmt.Printf("%p, %p, %v\n", slice, &slice, slice)
test(slice)
fmt.Printf("%p, %p, %v\n", slice, &slice, slice)
}
func test(slice []int) {
fmt.Printf("%p, %p, %v\n", slice, &slice, slice)
slice = append(slice, 1)
fmt.Printf("%p, %p, %v\n", slice, &slice, slice)
}
输出:
0x55d008, 0xc000010030, []
0x55d008, 0xc000010078, []
0xc00001c038, 0xc000010078, [1]
0x55d008, 0xc000010030, []
可以看出有几点符合预期的。
- slice和&slice的地址,在main函数中不变且不同
- slice在test中append前地址不变
但是有几点不符合预期:
- slice的内容在进入test后并没有变化
- &slice的值在进入test后发生了变化
- slice的地址在append后发生了变化
为什么会这样呢?其实本质是这样的
- golang的函数调用都是值传递,而传入的slice除了array是地址,len和capacity都是值
这样就可以解释了,由于值传递,所以有第4点,slice已经发生了copy;但由于array是地址,所以有第2点;但是由于len和capacity是值传递,所以都是0, 那么append操作会新申请内存并返回,所以有第5点,slice的地址发生了变化;并且由于main函数中len还是0,所以导致第3点,内容没有发生变化。
如果将capacity改大,如下代码,会有不同的输出
package main
import "fmt"
func main() {
slice := make([]int, 0, 10)
fmt.Printf("%p, %p, %v\n", slice, &slice, slice)
test(slice)
fmt.Printf("%p, %p, %v\n", slice, &slice, slice)
}
func test(slice []int) {
fmt.Printf("%p, %p, %v\n", slice, &slice, slice)
slice = append(slice, 1)
fmt.Printf("%p, %p, %v\n", slice, &slice, slice)
}
输出
0xc0000b2000, 0xc0000a8018, []
0xc0000b2000, 0xc0000a8060, []
0xc0000b2000, 0xc0000a8060, [1]
0xc0000b2000, 0xc0000a8018, []
结合之前内容,不难解释原因了吧。