根本原因是go所有参数均为值传递,p是原指针的副本;修改*p可改原值,但重赋值p只影响副本。常见错误如swap中交换指针变量本身无效,正确做法是解引用交换值。

为什么修改函数参数里的指针值,原变量有时没变?
根本原因在于:你传的是指针的副本,不是指针本身。Go 里所有参数都是值传递,func f(p *int) 中的 p 是调用时传入指针的一个拷贝。只要没对 *p 赋值,就不会影响原始变量。
常见错误现象:func swap(a, b *int) { a, b = b, a } 这样写完全无效——只是交换了两个指针副本的值,原始指针变量地址没变。
- 正确做法是解引用后赋值:
*a, *b = *b, *a - 如果想让函数内重新分配内存并让调用方感知,得传 **int(指向指针的指针)
- 结构体字段若为指针类型,直接修改
obj.field = newVal不会改变原结构体,但*obj.field = newVal会
什么时候该用 *T,什么时候该用 T?
核心判断依据是:是否需要在函数内修改调用方持有的原始数据,以及类型大小和复制开销。
使用场景对比:
- 小结构体(如
type Point struct{ x, y int })或基础类型(int,string),多数情况传T更清晰、更安全 - 大结构体(字段多、含 slice/map/interface)、或明确需要修改原值(如初始化、重置、状态变更),用
*T - 方法接收者选
*T的典型信号:方法内有t.field = ...或调用了其他指针方法 - 注意:map/slice/chan/channel 本身是引用类型,传
T就能修改底层数据,不需要额外加*
nil 指针解引用 panic 怎么提前发现?
运行时报 panic: runtime error: invalid memory address or nil pointer dereference 很常见,但多数能在编码阶段规避。
实操建议:
- 所有接收
*T参数的函数开头加防御性检查:if t == nil { return errors.New("t is nil") } - 构造函数(如
NewX())必须返回非 nil 指针,或明确文档说明可能返回 nil 并要求调用方检查 - 用静态检查工具:
go vet会提示部分明显未判空的解引用;staticcheck可配置SA5011规则捕获潜在 nil 解引用 - 测试用例必须覆盖 nil 输入路径,尤其边界逻辑(如初始化失败、配置缺失)
结构体内嵌指针字段的坑:为什么 JSON 反序列化后字段还是 nil?
典型问题:type User struct { Name *string `json:"name"` },反序列化 {"name":"Alice"} 后 user.Name 仍为 nil,因为 Go 的 json 包默认只对非 nil 指针字段赋值。
解决方式取决于需求:
- 想让
"name"字段存在就分配内存 → 改用json.RawMessage手动控制,或自定义UnmarshalJSON方法 - 接受零值语义(空字符串即
"")→ 直接用string类型,别用*string - 必须区分“未提供”和“提供了空字符串”→ 保持
*string,但反序列化前确保字段已初始化:u := &User{Name: new(string)} - 第三方库如
github.com/mitchellh/mapstructure对指针字段支持更友好,可考虑替代方案
指针真正的复杂点不在语法,而在你得时刻问自己:这个值,是我要它「指向别人」,还是「被别人指向」?漏掉这一层意图判断,后面所有调试都是在补前面的假设漏洞。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











