在 Go 语言中,所有函数的参数传递都是值传递(Pass by Value)。当你将一个变量作为参数传递给函数时,实际上传递的是该变量的副本,而不是变量本身。理解这一点对于避免常见的编程错误至关重要。根据不同的类型,以下作简要的区分和介绍
1. 基本类型(值类型)
例如:int, float, bool, string, struct 等类型。
函数内部操作的是原始值的副本,修改不会影响原变量。
func modifyValue(n int) {
n = 100 // 修改的是副本
}
func main() {
x := 10
modifyValue(x)
fmt.Println(x) // 输出 10,原值未变
}
2. 指针类型(T)
传递指针时,复制的是指针的副本,但副本和原指针指向同一块内存。
函数内通过指针修改的是原变量的值。
func modifyPointer(n *int) {
*n = 100 // 通过指针修改原值
}
func main() {
x := 10
modifyPointer(&x)
fmt.Println(x) // 输出 100
}
Tips:虽然指针是值传递,但通过间接访问可以修改原数据。
3. 引用类型(Slice、Map、Channel)
这些类型内部包含指向底层数据结构的指针,传递时复制的是它们的“描述符”(如切片的指针、长度、容量),而不是底层数据本身。
函数内对元素的修改会影响原变量,但修改描述符(如扩容切片)不会影响原变量。
func modifySlice(s []int) {
s[0] = 100 // 修改底层数组,影响原切片
s = append(s, 20 0) // 修改副本的描述符,原切片不受影响
}
func main() {
s := []int{1, 2, 3}
modifySlice(s)
fmt.Println(s) // 输出 [100 2 3]
}
注意误区:如果进行append操作导致扩容,如切片这时可能会新分配数组,这时候原切片和参数副本的行为就会不同,会导致修改行为不符合预期。但函数内对 Map 的修改始终影响原数据,因为 Map 本身就是引用类型。Channel一般不存在扩容场景。
关键总结
-
值传递的本质:所有参数传递都是复制值的副本。
-
指针和引用类型的表现:虽然传递的是副本,但通过指针或内部指针间接访问数据时,可以修改原数据。
-
性能考虑:
- 大型结构体使用指针传递避免复制开销。
- 切片、Map、Channel 本身是轻量级结构,直接传递即可。