Go 的函数参数传递都是值传递。所谓值传递:指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。参数传递还有引用传递,所谓引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
因为 Go 里面的 map,slice,chan 是引用类型。变量区分值类型和引用类型。所谓值类型:变量和变量的值存在同一个位置。所谓引用类型:变量和变量的值是不同的位置,变量的值存储的是对值的引用。
我们可以简单通过一段代码来理解“Go 的函数参数传递都是值传递”这个说法。首先我们都知道slice是引用类型。
package main
import "fmt"
func main() {
strSlice := make([]string, 0, 10)
strSlice = append(strSlice, "初始值")
//打印一下没有在函数内部修改的初始情况
fmt.Println("strSlice:", strSlice, "strSlice地址:", &strSlice[0], "len=", len(strSlice))
//在函数内部修改初始slice内容,再打印
change(strSlice)
fmt.Println("strSlice:", strSlice, "strSlice地址:", &strSlice[0], "len=", len(strSlice))
//fmt.Println(strSlice[1])
}
func change(str []string) {
fmt.Println("函数传参地址:", &str[0])
str[0] = "修改后"
fmt.Println("函数传参地址:", &str[0])
fmt.Println("形参str长度:", len(str))
}
输出结果为:
strSlice: [初始值] strSlice地址: 0xc0000720a0 len= 1
函数传参地址: 0xc0000720a0
函数传参地址: 0xc0000720a0
形参str长度: 1
strSlice: [修改后] strSlice地址: 0xc0000720a0 len= 1
我们可以看到,似乎这里看起来是引用传递,函数对传入的slice进行修改后,会对原slice产生相同的影响,我们可以看看下面这段代码:
package main
import "fmt"
func main() {
strSlice := make([]string, 0, 10)
strSlice = append(strSlice, "初始值")
//打印一下没有在函数内部修改的初始情况
fmt.Println("strSlice:", strSlice, "strSlice地址:", &strSlice[0], "len=", len(strSlice))
//在函数内部修改初始slice内容,再打印
change(strSlice)
fmt.Println("strSlice:", strSlice, "strSlice地址:", &strSlice[0], "len=", len(strSlice))
//fmt.Println(strSlice[1])
}
func change(str []string) {
fmt.Println("函数传参地址:", &str[0])
str = append(str, "新增一个内容")
fmt.Println("函数传参地址:", &str[0])
fmt.Println("形参str长度:", len(str))
}
输出结果:
strSlice: [初始值] strSlice地址: 0xc0000720a0 len= 1
函数传参地址: 0xc0000720a0
函数传参地址: 0xc0000720a0
形参str长度: 2
strSlice: [初始值] strSlice地址: 0xc0000720a0 len= 1
因为slice的扩容机制的影响,可能会出现slice的地址(切片对象在内存的地址和指向底层数组的地址)发生变化,我们把对应的容量设置为10,方便我们进行测试。我们会发现,当我们使用append方法在原slice添加多一个元素时,slice的地址并没有发生变化,在函数内的slice的len为2,但是原slice还是len=1,我们可以从slice的结构来分析:
//go 1.20.3 path: /src/runtime/slice.go
type slice struct {
array unsafe.Pointer
len int
cap int
}
我们就会得到下面这种情况:
也就是, array 是一个指向底层数组的指针,这个数组存储着切片中的元素。len 表示切片的长度,即切片中元素的数量。cap 表示切片的容量,即切片底层数组中可用的元素数量。golang的函数传参都是值传递,即使传递的是引用类型,也是对应引用类型的地址拷贝。因此,第一个案例中,实际上是把指向底层数组的指针的地址拷贝生成一个副本传到了函数体中,所以,第一个案例中修改了0xc0000720a0地址里的内容会引发外面的参数发生变化