根据之前学习python和java的经验,每次学习一门新语言时,一定要搞清楚方法的参数传递是值传递,引用传递还是指针传递。
主要原因就是需要知道,某种类型的数据传递给某个方法后,方法里面对它的修改是否会影响到这个数据本身。
结论
在 Go 语言中,所有方法的参数传递都是值传递(Pass by Value)。这意味着无论是基本类型(如 int、string)、结构体,还是引用类型(如切片、映射、通道、指针等),传递给方法的始终是参数的副本。不过,对于引用类型或指针来说,虽然传递的是副本,但这些副本中保存的是指向底层数据结构的指针,因此可能表现出类似"引用传递"的行为。
基础数据类型
像字符串类型,数值类型就不用多说,传递到方法中的是拷贝。
struct
传递到方法中的是拷贝。
slice
切片本质上也是一个结构体。在runtime/slice.go中有如下定义
go
type slice struct {
array unsafe.Pointer
len int
cap int
}
这也就解释了如下情况。
go
func main() {
var s1, s2 = []int{1, 2, 3}, []int{1, 2, 3}
modify1(s1)
fmt.Println(s1) // [588,2,3]
modify2(s2)
fmt.Println(s2) // [1,2,3]
}
func modify1(a []int) {
a[0] = 588 // 可以修改原来切片s的第一个值为588,即s={588,2,3},因为是通过指针修改的unsafe.Pointer
}
func modify2(a []int) {
a = a[:2] // 此时无法修改成功使s={2,3},因为a是s副本
}
常见误区
-
"切片是引用传递"
切片传递的是描述符的副本(包含指向底层数组的指针),因此对底层数组的修改会影响原切片。但如果是修改切片的长度或容量(如 append),则不会影响原切片,除非显式返回并覆盖。
-
"映射和通道是引用类型"
映射和通道的行为类似指针,但严格来说它们仍然是值传递。它们的底层数据结构在堆上分配,传递的是指向堆内存的指针的副本。
-
切片的指针传递*mySlice = otherSlice操作
go
func main() {
mySlice := []int{1, 2, 3}
modify1(&mySlice)
fmt.Println(mySlice) // [1,2,3]
modify2(&mySlice)
fmt.Println(mySlice) // [87,34,99]
}
func modify1(mySlice *[]int) {
otherSlice := []int{87, 34, 999}
mySlice = &otherSlice // 将mySlice指针指向otherSlice的地址
}
func modify2(mySlice *[]int) {
otherSlice := []int{87, 34, 999}
*mySlice = otherSlice // 修改mySlice指针指向的地址的切片数据为otherSlice
}
