在Go语言中,函数参数的传递方式常常让新手感到困惑。实际上Go只有一种参数传递规则,那就是值传递。无论是传递基本类型、结构体还是指针,调用的那一刻都会发生一次拷贝。传值与传指针调用的核心差异,在于拷贝的内容以及函数体内能否影响到原数据。

什么是值传递
值传递意味着把实参的副本交给函数的形参。对于普通变量,副本就是数据的拷贝;对于指针变量,副本就是同一个内存地址。下面用例子说明传值的情况。
package main
import "fmt"
// 传值:拷贝了num的值
func addOne(num int) {
num = num + 1
fmt.Println("函数内 num:", num)
}
func main() {
a := 10
addOne(a)
fmt.Println("函数外 a:", a) // 仍然是10
}
上述代码中,addOne接收到的是a的副本,修改副本不会影响原来的a。
传指针时发生了什么
传指针并不是特殊的引用传递,而是把指针变量的值(也就是地址)做了一次拷贝。因为两个指针指向同一块内存,所以函数内可以通过解引用修改原对象。
package main
import "fmt"
// 传指针:拷贝了地址,可通过地址改原值
func addOneByPtr(num *int) {
*num = *num + 1
fmt.Println("函数内 *num:", *num)
}
func main() {
a := 10
addOneByPtr(&a)
fmt.Println("函数外 a:", a) // 变成11
}
两者的主要区别
- 传值会复制整个对象,结构体较大时开销明显;传指针只复制一个机器字长的地址。
- 传值函数内修改不影响原值,语义更安全;传指针可修改原值,但需要小心并发和意外副作用。
- 传指针也可能遇到空指针问题,调用方需保证传入合法地址。
什么时候用哪种
| 场景 | 推荐方式 |
|---|---|
| 小型基础类型且不需修改原值 | 传值 |
| 大结构体或需要修改原对象 | 传指针 |
| 方法接收者且对象会变更 | 传指针 |
使用指针减少拷贝的示例
package main
import "fmt"
type User struct {
Name string
Age int
}
// 传指针避免拷贝整个User
func growUp(u *User) {
u.Age = u.Age + 1
}
func main() {
u := User{Name: "Tom", Age: 20}
growUp(&u)
fmt.Println(u.Age) // 21
}
小结
Go的函数参数传递统一是值传递。传指针调用只是值传递的一种特例,拷贝的是地址而非对象本身。理解这一点后,就能根据对象大小、是否需修改原值以及安全性要求,合理选择传值还是传指针,写出更清晰的Go代码。