结构体指针初始化有三种合法方式:&Struct{}(推荐,可初始化字段)、new(Struct)(返回全零值指针)、先声明变量再取地址(适用于需复用变量名或多次修改场景);不可取字面量地址。

结构体指针怎么初始化:&、new 和变量取址三选一
Go里创建结构体指针就三种合法方式,选哪个取决于你是否需要初始化字段、是否要复用变量名,以及是否接受零值。
-
&Struct{}最常用:直接构造并初始化字段,比如p := &Person{Name: "Alice", Age: 30};字段没写的自动为零值,语法清晰,推荐日常首选 -
new(Struct)返回零值指针:所有字段都是默认值(Name=="",Age==0,Field *T == nil),适合后续再逐个赋值,或你明确只需要一个空壳指针 - 先声明变量再取地址:比如
u := Person{Name: "Bob"}; ptr := &u,适用于需要多次修改u再传指针、或需在作用域内保留原变量名的场景
注意:&42 或 &"hello" 是非法的——Go 不允许取字面量地址,所以基本类型指针字段(如 *int)不能用 &42 初始化,得用 new(int) 或先声明变量再取址。
访问字段为什么不用 *ptr.field:Go 的自动解引用机制
你写 ptr.Name 就行,根本不用写 (*ptr).Name,更不能写 *ptr.Name——后者会报错 invalid indirect of ptr.Name (type string)。
原因很简单:Go 编译器看到 ptr 是 *Struct 类型,就自动把 ptr.Field 翻译成 (*ptr).Field。这不是“省略”,而是语言级语法糖,和 C 的 -> 本质等价,但更简洁。
立即学习“go语言免费学习笔记(深入)”;
对比一下基本类型指针就明白了:
strPtr := new(string)
*strPtr = "ok" // ✅ 必须加 *
personPtr := new(Person)
personPtr.Name = "ok" // ✅ 自动解引用,不加 *
// personPtr.Name = "ok" 等价于 (*personPtr).Name = "ok"
初学者常在这里栽跟头:看到 new 就以为所有指针都要手动解引用,其实只对非结构体才强制要求。
方法接收者用 *T 还是 T:改不改原值、大不大、接不接口
决定方法接收者类型,核心就看三点:要不要改结构体本身?结构体够不够大?有没有实现接口?
- 要修改字段 → 必须用
func (p *Person) SetAge(a int);值接收者(p Person)改的只是副本,原结构体完全不受影响 - 结构体字段多、含 slice/map/大数组 → 用指针避免拷贝开销(哪怕只有几十字节,一致性也值得)
- 已有一个方法用了指针接收者 → 其他方法最好也统一用指针,否则可能因方法集不一致导致接口实现失败
Go 会自动帮你转换调用:变量 u 调 u.Method(),即使 Method 是指针接收者,只要 u 可寻址(比如是变量不是字面量),编译器就悄悄转成 (&u).Method()。但 Person{Name:"x"}.Method()(临时字面量)就不能调指针接收者方法。
结构体里字段该不该用指针:别为了指针而指针
结构体字段声明为指针(如 Avatar *Image),不是风格选择,而是建模意图表达。
- 字段可能为空(如可选地址、未上传头像)→ 用
*T,nil就是天然的“未设置”语义 - 字段类型很大(比如含几百 KB 的
[]byte或嵌套深的结构体)→ 用指针避免每次赋值都拷贝 - 需要共享同一份数据(多个结构体实例共用一个配置对象)→ 指针让修改一处、处处可见
- 切片(
[]T)、map(map[K]V)、channel、function 本身已是引用头,字段直接用值类型即可,再包一层*[]T反而多余且易出错
访问前务必判空:if u.Avatar != nil { u.Avatar.Resize() };否则 u.Avatar.URL 会 panic。建议把这类逻辑封装进方法里,比如 u.AvatarURL(),内部处理 nil 分支,调用方就不用每次都想。
最常被忽略的是:字段指针默认就是 nil,不初始化就用,运行时就崩——这和结构体指针变量本身是不是 nil 是两回事,得一个个字段盯紧。










