结构体指针初始化有三种合法方式:&struct{}(推荐,可初始化字段)、new(struct)(返回全零值指针)、先声明变量再取地址;不可取字面量地址,如&42或&"hello"非法。

结构体指针初始化不能用 &字面量
Go 不允许对字面量取地址,所以 &Person{Name: "A"} 合法,但 &"hello"、&42、&[]int{1,2} 全部编译报错。这个限制直接影响结构体中基本类型指针字段的初始化。
常见错误现象:Config struct{ Timeout *int },想直接写 cfg := Config{Timeout: &30} —— 编译失败,提示 cannot take the address of 30。
- 正确做法:用
new(int),再赋值:timeout := new(int); *timeout = 30; cfg := Config{Timeout: timeout} - 更简洁写法:先声明变量再取址:
sec := 30; cfg := Config{Timeout: &sec} - 若只是临时传参,可封装为辅助函数:
func intp(v int) *int { return &v },然后Config{Timeout: intp(30)}
方法接收者混用 *T 和 T 会导致接口实现断裂
一旦结构体某个方法用了指针接收者(比如 func (p *User) Save()),其他方法就**必须统一用 *User 接收者**,否则该类型无法完整实现接口。
使用场景:你定义了 Storer 接口,要求 Save() 和 Validate() 都实现。如果 Validate 用值接收者,那 *User 类型能赋给 Storer,但 User 类型不能——因为 *User 实现了全部方法,而 User 只实现了 Validate,没实现 Save。
- 值接收者方法只能被
User调用;指针接收者方法只能被*User调用 - 接口变量接收
*User时,调用的是指针方法;接收User时,只能调用值方法 - 编译器不会报错,但运行时可能 panic 或静默跳过方法调用(尤其在反射或依赖注入如 Wire 中)
JSON 反序列化时 *string 和 string 字段行为完全不同
json.Unmarshal 对指针字段和值字段的“缺失”语义处理是根本不同的:指针字段为 nil 表示“字段未提供”,值字段为零值(如 "")表示“字段存在但为空”。这是 API 开发中最容易踩坑的地方。
常见错误现象:用 Name string 字段接收 JSON,发现 {"name":""} 和 {} 无法区分,都变成空字符串,导致业务逻辑误判。
- 要支持“可选 + 显式空”,必须用
Name *string - 反序列化后检查
if req.Name != nil→ 字段提供了(哪怕值是"");if req.Name == nil→ 字段完全缺失 - 数据库 ORM 场景同理:
CreatedAt *time.Time可区分“未设置”和“设为 Unix 零点” - 注意:
omitemptytag 只控制序列化输出,不影响反序列化逻辑
nil 指针调用方法前不检查就会 panic
Go 在调用方法前会自动解引用指针接收者。也就是说,var u *User = nil; u.GetName() 这行代码在进入 GetName 前就崩溃了,不是方法体内出错。
这和 Java 的 NullPointerException 不同:Go 的 panic 发生在方法调用入口,且没有栈内方法体上下文,调试时容易误判位置。
- 所有指针接收者方法,调用前必须显式检查:
if u != nil { u.GetName() } - 不要依赖“方法里做了非空判断”——panic 已经发生在进方法之前
- 接口变量持有
*User时同样适用:var s Storer = u; s.Save(),若u == nil,这里就 panic - Wire 等 DI 框架注入的指针,也需确认 provider 是否可能返回 nil
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











