Go 没有继承,嵌入是“组合 + 自动转发”。这篇用真实输出看清它到底做了什么:提升的字段与方法、遮蔽、嵌入指针与嵌入值的方法集、嵌入接口做装饰器、与 encoding/json 的交互,以及容易误以为有的“虚函数”其实没有。
d.Name = "旺财"(提升的字段);d.Animal.Name = "旺财";d.Rename("小黄") 之后 d.Name = "小黄"
Dog 自己定义了 Speak:d.Speak() = "小黄 汪汪";d.Animal.Speak() = "小黄 发出声音"
d.Intro()(Animal 的方法,里面调用 a.Speak()):"我是 小黄,我的声音:小黄 发出声音" ——不会调用 Dog 的 Speak(没有虚函数分派)嵌入字段的字段与方法会被提升到外层,可以直接 d.Name、d.Rename(...)。外层定义同名方法时遮蔽被嵌入的那个,但嵌入的方法并没有消失,可以通过 d.Animal.Speak() 访问。更深的层级里同名会被更浅的遮蔽;同一深度的同名字段是歧义,编译器拒绝(第五节)。
Animal 的方法集:Intro,Speak;*Animal 的方法集:Intro,Rename,Speak Dog(嵌入 Animal)的方法集:Intro,Speak;*Dog 的方法集:Intro,Rename,Speak Cat(嵌入 *Animal)的方法集:Intro,Rename,Speak;*Cat 的方法集:Intro,Rename,Speak
关键的一点:Animal.Intro 的接收者是 Animal,它里面的 a.Speak() 永远是 Animal.Speak,不会因为外面包了一层 Dog 就改调 Dog.Speak。要想让被嵌入类型的方法能调用到外层的“重写”,必须把外层的行为作为接口或函数字段传进去。
经 countWriter 写了 "hello, world!",统计 n = 13,底层缓冲区内容 "hello, world!" nil 的嵌入接口:cw2.Write 调用 → panic:runtime error: invalid memory address or nil pointer dereference
嵌入 io.Writer 的结构体自动拥有 Write,再改写一个方法就是一个装饰器,其余方法保持不变。风险是嵌入的接口没有初始化:调用被转发的方法会解引用 nil,panic。
json.Marshal(Outer{Inner{7, ...}, ...}) = {"id":7,"note":"内层备注","name":"名称","outer_note":"外层备注"}
Outer.Note 解析到的字段:index [2](外层的 Note);Outer.ID 解析到:index [0 0](提升自 Inner)
reflect 报告 Outer 有 3 个顶层字段(Inner、Name、Note),其中 Inner 的 Anonymous = trueencoding/json 把匿名嵌入的结构体当作字段展开到外层;同名字段按层级与 tag 规则选择。reflect 里能看到嵌入字段的 Anonymous 为 true,以及 FieldByName 返回的是多层的下标路径。这里只展示了最简单的情形,同名字段冲突时的完整规则在 encoding/json 的文档里,我没有逐条验证。
main.go:19:8: ambiguous selector c.X
main.go:20:16: cannot use Q{} (value of struct type Q) as Incer value in variable declaration: Q does not implement Incer (method Inc has pointer receiver)歧义选择器(同深度同名)与“值没有指针方法”的接口赋值,都是编译期就报错,不会拖到运行时。