Go函数参数均为值传递,修改原变量必须传指针;切片等类型虽有引用语义,但重赋值仍需指针;new(T)创建零值指针,&v取已有变量地址;指针参数须手动检查nil以防panic。

Go 函数参数是值传递,指针才能改原变量
Go 里所有函数参数都是值传递,哪怕传的是 struct 或大数组,也会复制一份。想在函数里修改调用方的原始数据,必须传指针。这不是“可选优化”,而是唯一方式。
常见错误是传值后试图修改,比如:
func modifyName(u User) {
u.Name = "Alice" // 对 u 的修改不会影响调用方的 u
}
这行赋值只改了副本,原变量毫发无损。
什么时候必须用 *T 作参数类型
以下场景不传指针就无法达成目标:
立即学习“go语言免费学习笔记(深入)”;
- 需要修改结构体字段(如更新
User.Status、Config.Timeout) - 函数要“返回多个可变结果”,且其中某些是输入变量的就地更新(比如解析 JSON 到已分配的
*map[string]interface{}) - 避免复制大对象(如几 MB 的
[]byte或嵌套深的map),这时传*[]byte比传[]byte更省内存和时间
注意:切片([]T)、map、channel、function、interface 本身已含引用语义,传它们时修改底层数据(如 slice[0] = x、m["k"] = v)会影响原值,但重赋值(如 slice = append(slice, x))会断开连接——此时仍需指针才能让调用方看到新底层数组。
new(T) 和 &v 的区别与选择
获取指针有两种常用写法,语义和适用场景不同:
-
new(T)返回一个指向新分配的零值T的指针,等价于var v T; return &v。适合你只需要一个空结构体指针,且不想声明临时变量 -
&v取已有变量地址,要求v是可寻址的(不能是字面量、函数返回值、map 索引表达式等)。这是最常用的方式,比如modify(&user)
错误示例:modify(&User{Name: "Bob"}) 会报错 cannot take the address of User literal;应先声明再取址,或改用 new(User) 后再赋值字段。
指针参数的 nil 安全性必须手动检查
Go 不做空指针自动防护。如果函数接收 *T,而调用方传了 nil,直接解引用(如 u.Name = "x")会 panic:invalid memory address or nil pointer dereference。
务必在函数开头显式判断:
func updateUser(u *User) {
if u == nil {
return // 或 panic("u is nil"), 视业务逻辑而定
}
u.Name = "Updated"
}
这个检查容易被忽略,尤其当指针来自配置加载、JSON 解析(json.Unmarshal 对空字段默认设为 nil)或 map 查找时。不加判断,线上 panic 就在下一秒。










