Go中所有参数均为值传递,包括指针;传指针能否修改原值取决于是否解引用赋值(*p = x),而非单纯传地址;nil指针需显式检查,含引用字段的结构体传值仍可能意外共享底层数据。

Go 里没有“指针参数传递”这回事——func f(p *T) 传进去的只是那个 8 字节地址的副本,和传 int、string 一样,都是值传递。真正要问的不是“流程怎么走”,而是“改不改得到原值”“为什么有时改了却没生效”。
为什么 &v 传进函数后,*v = x 却没改外面?
常见于误以为“传了指针就一定能改”,但实际在函数内做了覆盖赋值:
-
p = &newVal:只改了副本p指向的新地址,原指针变量仍指向旧内存 -
*p = newVal:这才是正确写法,直接写入p所指的那块内存 - 结构体字段是
map或[]T:传*MyStruct后,s.Map["k"] = v有效,但s.Slice = append(s.Slice, x)只改副本 header,原 slice 不变
json.Unmarshal(data, &v) 为什么必须加 &?
因为 json.Unmarshal 签名是 func Unmarshal(data []byte, v interface{}) error,而内部会用反射往 v 指向的内存填字段。不加 & 就等于传一个未取址的 v,v 是值类型时无法写入,编译直接报错:cannot use v (type T) as type *T in argument to json.Unmarshal。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 同理适用于
binary.Read、sql.Rows.Scan、proto.Unmarshal - 如果
v已是*T类型(比如var v *User),就不用再写&v,否则变成**T,类型不匹配
传 *T 却 panic: invalid memory address?
因为 *T 可能是 nil,而 Go 不自动判空。一旦执行 *p 或 p.Field 就崩溃。
立即学习“go语言免费学习笔记(深入)”;
- 函数开头第一行就该检查:
if p == nil { return errors.New("p is nil") } - HTTP handler、JSON unmarshal、DB scan 都可能产出
nil指针,别依赖“调用方不会传 nil” - 若逻辑允许
nil(如可选配置),也得显式分支:if p != nil { p.Count++ },不能放任解引用
结构体传值 vs 传指针:最隐蔽的共享陷阱
你以为 func f(u User) 是完全隔离的副本?错。只要 User 含 Data []byte 或 Meta map[string]string,那么 u.Data[0] = 99 会直接改原对象底层数组;但 u.Data = append(u.Data, x) 不一定——因为 append 可能扩容并返回新 header,原 u.Data 不受影响。
- 想彻底隔离,得手动深拷贝:
newData := make([]byte, len(u.Data)); copy(newData, u.Data) - 更可持续的做法:把引用字段设为私有,暴露
GetData() []byte(返回副本)和SetData()(带校验) - 别迷信“传值就绝对安全”——Go 的值传递只复制结构体头,不递归复制其引用字段内容

















