判断 nil 指针的正确写法是 if p == nil,不可用 if !p(语法错误)或其它变体;解引用前必须显式检查,尤其注意接口非空不等于指针非空、嵌套字段需逐级判空、切片/map 中指针元素需单独检查。

判断 nil 指针的正确写法
Go 中指针变量未初始化时默认是 nil,但直接解引用 nil 会导致 panic:"invalid memory address or nil pointer dereference"。安全的第一步永远是显式比较:if p == nil —— 不要用 if !p(语法错误),也不能用 if p != nil 以外的任何变体去“取巧”。
常见错误现象:在结构体字段、函数返回值、map 查找结果中拿到一个指针后,没检查就直接访问 p.Field,运行时报 panic。
-
nil是预声明标识符,类型为nil的指针与任意具体指针类型(如*string、*User)可比较,无需类型断言 - 接口变量里存了
*T值,但该指针本身是nil,此时接口变量不为nil—— 这是高频踩坑点:接口非空 ≠ 指针非空 - 切片、map、channel 的零值也是
nil,但它们不是指针类型,不能和指针混为一谈
if p == nil 之后怎么安全解引用
检查通过后,解引用本身没有额外技巧,但要注意上下文是否允许修改原值。比如函数参数传入 *int,你解引用后赋值 *p = 42 是合法的;但如果该指针来自只读场景(如从 JSON 解析出的嵌套结构体字段),盲目修改可能破坏数据一致性。
使用场景典型包括:HTTP handler 中解析请求体得到 *User,数据库查询返回 *Order,或配置项允许字段为空时用指针表示缺失。
立即学习“go语言免费学习笔记(深入)”;
- 不要在一行里连写
if p != nil { doSomething(*p) }后紧接着*p = ...—— 即使前面判了非空,中间若被并发写入或重置,仍可能 panic(虽少见,但真实存在) - 如果逻辑需要多次解引用,建议提前赋值:
val := *p,后续用val,避免重复解引用开销(微小但确定) - 对
interface{}类型变量,先类型断言再判空:if u, ok := v.(*User); ok && u != nil
结构体字段为指针时的空值处理惯用法
当结构体字段定义为 *string 或 *time.Time,意味着该字段可选。这时不能假设它有值,尤其在 JSON 反序列化后:空 JSON 字段会得到 nil 指针,而非零值。
性能影响很小,但兼容性上要注意:gRPC、OpenAPI 等协议常将 optional 字段映射为指针,而前端传 null 就对应后端 nil。
- 别用
fmt.Sprintf("%v", p)判断是否为空 ——nil指针打印出来是<nil></nil>,但字符串匹配不可靠,且有额外分配 - 给指针字段提供 “Get” 方法是常见做法:
func (u *User) Name() string { if u.Name == nil { return "" } else { return *u.Name } } - 数据库 ORM(如 GORM)对零值字段默认跳过更新,但如果字段是
*string且设为nil,会明确更新为 NULL —— 这和普通字段行为不同,需留意
容易被忽略的边界:数组、切片、map 中的指针元素
数组或切片元素类型为指针(如 []*User),其每个元素都可能是 nil。遍历时若不做检查,for _, u := range users { fmt.Println(u.Name) } 会在某个 u == nil 时 panic。
这不是 Go 的 bug,而是设计使然:指针就是可以为空的引用,语言不替你做隐式空值保护。
- 循环内必须单独检查:
if u != nil { fmt.Println(u.Name) } - map 的 value 是指针时同理:
v, ok := m[key]; if ok && v != nil——ok只说明 key 存在,不保证 value 非空 - 用
sync.Map存指针?同样适用:value 是interface{},取出后要先断言再判空
最麻烦的不是写 == nil,而是忘记在深层嵌套里检查——比如 req.User.Profile.AvatarURL,其中任意一级是 nil 都会崩。这时候要么分步判空,要么用工具生成带空检查的访问函数,别硬扛。










