go指针错误90%因未判nil即解引用,需在*操作、方法调用、字段访问前检查;其余10%源于循环变量地址复用、并发裸共享指针、接口变量误判nil。

直接说结论:Go里指针出错,90%是因为没判 nil 就解引用,剩下10%是循环变量地址复用、并发裸共享指针、或误以为接口变量为 nil。
解引用前必须检查 nil
这是最常见、最直接的 panic 来源。任何对 *T 类型变量做 * 操作、调用方法、访问字段前,都得先确认它不为 nil。
- 函数参数是
*User?第一行就该写if u == nil { return fmt.Errorf("user is nil") } - 结构体字段是
*string?访问前必须写if u.MiddleName != nil { fmt.Println(*u.MiddleName) } - 从
map[string]interface{}断言出*Config?不能跳步:if c, ok := data.(*Config); ok && c != nil { ... } - HTTP 请求后拿到
*http.Response?必须先if err != nil,再用resp.Body;否则resp为nil时resp.Body.Close()立刻 panic
for 循环中取局部变量地址会复用栈空间
在循环里反复声明同名变量再取地址,Go 编译器可能复用同一块栈内存,导致所有指针指向最后一次迭代的值——表面没报错,但数据全错。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 错误写法:
p := Person{Name: name}; people = append(people, &p)——people里所有元素地址相同 - 验证方式:打印地址
fmt.Printf("%p\n", people[i]),若全部一致就是踩坑了 - 正确做法:改用
&Person{Name: name}(编译器自动逃逸到堆),或把变量声明提到循环外再每次赋值(需确保生命周期足够)
map 存指针 + 并发读写 = 隐形竞态
给 map[string]*User 加 sync.Mutex 只保护 map 本身的增删改查,不保护 *User 指向的结构体字段。多个 goroutine 同时改 users["a"].Name,照样数据竞争。
- 单纯锁 map 不够,真正要同步的是结构体字段访问
- 稳妥方案:避免在 map 中存可变指针;若必须,优先封装成带锁的方法(如
u.SetName()),把同步逻辑收口 - 检测手段:
go run -race能暴露这类问题,输出会明确指出哪两个 goroutine 在读写同一内存地址
接口变量赋了 nil 指针 ≠ 接口本身为 nil
这是个经典陷阱:var u *User; i := interface{}(u),此时 i 不是 nil(它包含类型 *User 和值 nil),所以 i == nil 是 false。
- 错误判断:
if i == nil无法捕获这种“非空接口含空指针”的情况 - 正确判断(不推荐):
reflect.ValueOf(i).IsNil(),但反射开销大且语义模糊 - 更优解:设计上避免把裸指针塞进接口;或统一用 error 返回失败,而非靠接口判空
最易被忽略的一点:校验顺序不能颠倒。比如 s.GetUser(id) 返回 (user *User, err error),必须先 if err != nil,再 if user == nil——因为某些 SDK 在 error 为 nil 时仍可能返回 nil 用户(如缓存穿透未命中),而一旦顺序反了,err == nil 时 user 还没检查就直接用了,panic 就来了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










