
Go 中使用 %v 或 %+v 打印结构体指针时,默认只显示嵌套字段的内存地址;若需展开打印指针所指向结构体的内容,需手动控制格式化输出,或为相关类型实现 fmt.Stringer 接口。
go 中使用 `%v` 或 `%+v` 打印结构体指针时,默认只显示嵌套字段的内存地址;若需展开打印指针所指向结构体的内容,需手动控制格式化输出,或为相关类型实现 `fmt.stringer` 接口。
在 Go 语言中,当结构体字段包含指向其他结构体的指针(如 *B)时,直接对持有该指针的结构体使用 fmt.Printf("%v", a) 或 fmt.Printf("%+v", a),只会输出指针地址(例如 0xc42000e204),而不会自动解引用并展示目标结构体的内容。这是 Go 的默认行为——它尊重指针语义,避免隐式解引用带来的歧义与性能开销。
要实现“展开打印嵌套结构体值”,有以下两种主流、推荐的方式:
✅ 方式一:为结构体实现 fmt.Stringer 接口(推荐用于复用场景)
通过为 *A 和 *B 分别定义 String() string 方法,即可让 fmt 包在遇到 %v 格式动词时自动调用该方法,从而统一、可控地定制输出格式:
func (aa *A) String() string {
return fmt.Sprintf("A{a:%d, B:%v}", aa.a, aa.B)
}
func (bb *B) String() string {
return fmt.Sprintf("B{b:%d}", bb.b)
}此时调用 fmt.Printf("v ==== %v\n", a) 将输出:
v ==== A{a:1, B:B{b:2}}✅ 优势:一次实现,处处生效;支持递归展开(只要嵌套类型也实现了 Stringer);符合 Go 的接口设计哲学。
✅ 方式二:显式解引用 + 自定义格式字符串(适合临时调试)
直接在 fmt.Printf 中访问字段并解引用,例如:
fmt.Printf("v ==== A{a:%d, B:B{b:%d}}\n", a.a, a.B.b)
// 输出:v ==== A{a:1, B:B{b:2}}或结合 %v 让 B 的 String() 生效(前提是已实现):
fmt.Printf("v ==== A{a:%d, B:%v}\n", a.a, a.B) // 此时 a.B 会触发 *B.String()⚠️ 注意:若未实现 Stringer,%v 对 a.B 仍会打印地址;且深度嵌套时(如 *A → *B → *C)手动展开易出错、难维护。
? 补充说明
-
fmt.Printf("%+v", a)中的+仅表示“显示字段名”,不改变指针的打印行为; - 不建议使用反射(
reflect)做通用展开——既复杂又丧失类型安全,且无法控制输出格式; - 若结构体含循环引用(如
A持有*B,B又持有*A),Stringer实现中需注意避免无限递归。
总之,优先为关键结构体实现 Stringer 接口,兼顾可读性、可维护性与一致性;调试阶段可辅以显式解引用快速验证。这是 Go 中处理结构体指针格式化输出的惯用、地道实践。


















