切片传参幻觉:传引用
golang中函数的参数为切片时是传引用还是传值?对于这个问题,当你百度一轮过后,你会发现很大一部分人认为是传引用,通常他们会贴出下面这段代码进行佐证
pacakge main
func changeSlice(s []int) {
s[1] = 111
}
func main() {
slice := []int{0, 1, 2, 3}
fmt.Printf("slice: %v \n", slice)
changeSlice(slice)
fmt.Printf("slice: %v\n", slice)
}
从输出结果我们看到,函数changeSlice内对切片的修改,main函数中的切片变量slice也跟着修改了。咋一看,这不就是引用传递的表现吗?
三个概念
- 传值
- 传指针
- 传引用
传值没什么好说的,传指针和传引用还是有区别的
传指针时,值虽然相同,但是存放这两个指针的内存地址是不同的,因此这是两个不同的指针。
任何存放在内存里的东西都有自己的地址,指针也不例外,它虽然指向别的数据,但是也有存放该指针的内存。
传引用时,将实际参数的地址传递到函数中,在函数中对参数所进行的修改,将影响实际参数。
但是,在Go中无法举例说明传引用
官方:Go函数传参只有值传递
Slice结构体
type slice struct {
array unsafe.Pointer
len int
cap int
}
type Pointer *ArbitraryType
第一部分是指向底层数组的指针,其次是切片的大小len和切片的容量cap。(果然含有指针成员变量。)
分析
Go里面函数传参只有值传递一种方式,所以当切片作为参数时,其实也是切片的拷贝,但是在拷贝的切片中,其包含的指针成员变量的值是一样的,也就是说它们指向的数据源是一样,因此在调用函数内修改形参能影响实参。
于是我们又可以这样总结:
Go语言中所有的传参都是值传递(传值),都是一个副本,一个拷贝。因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中就无法修改原内容数据;有的是引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据。
这里要注意的是:引用类型和传引用是两个概念。
切片参数,修改形参一定影响实参吗
前些天遇到这样的问题
func main(){
s := make([]int, 0)
fmt.Println(len(s))
AlterSlice(s)
fmt.Println(len(s))
}
func AlterSlice(s []int){
s = append(s, 1)
s = append(s, 2)
fmt.Println(len(s))
}
期望在AlterSlice之后输出长度为2,可实际打印的长度为0,这又是怎么回事?
函数内修改形参s后,外部的实参slice并没有跟着改变,而且注意到一点是,通过地址打印,发现形参和实参的指针成员变量的值是不一样的,也就是说它们指向的数据源不是同一个了。
这又是为什么呢?
扩容
当需要扩容时,append会做哪些操作呢?
- 创建一个新的临时切片t,t的长度和slice切片的长度一样,但是t的容量是slice切片的2倍,新建切片的时候,底层也创建了一个匿名的数组,数组的长度和切片容量一样。
- 复制slice里面的元素到t里,即填入匿名数组中。然后把t赋值给slice,现在slice的指向了底层的匿名数组。
- 转变成小于容量的append方法。
举个例子,数组arr = [3]int{0, 11, 22},生成一个切片slice := arr[1:3],使用append方法往切片slice中追加元素33,将发生以下操作:
再回到刚刚举的例子,之所外部的实参切片变量slice不受形参切片变量s修改的影响,因为在执行完 s = append(s, 1) 这段代码后,切片s的指针指向的数组发生了扩容,其指针指向了新的数组,因此当再次修改其第二个元素的值时,是不会影响外部切片变量slice的。