编程后端开发者

嵌套结构体(embedding structs)在 Go 中是如何工作的?嵌入和常规结构体包含有什么区别?需要注意哪些细节?

用 Hintsage AI 助手通过面试

回答

在 Go 中,嵌入(embedding)是一种机制,允许通过将一个结构体嵌入到另一个结构体中来“继承”结构体的行为,而无需使用显式的继承,例如在 OOP 中。嵌入的结构体成为新结构体的嵌入字段(匿名字段)。

在这种情况下,嵌入结构体的所有方法都成为新结构体的方法,仿佛被“继承”了一样。

示例:

package main import "fmt" type Animal struct { Name string } func (a Animal) Speak() { fmt.Println("My name is", a.Name) } type Dog struct { Animal // embedding, 匿名字段 Breed string } func main() { d := Dog{ Animal: Animal{Name: "Rex"}, Breed: "Shepherd", } d.Speak() // 继承的方法! fmt.Println(d.Name) // 字段可以直接访问 }

嵌入和将结构体作为命名字段的常规包含之间的主要区别是:

  • 在嵌入中,方法和字段可以直接在新结构体中访问,
  • 在常规包含中,需要通过字段名访问它们。

陷阱问题

“如果嵌入结构体和外部结构体包含相同名称的字段,将使用哪个字段,如何访问它们?”

许多人认为只会使用“外部”字段,但实际情况是:

  • 如果名称相同,将使用外部层级,而可以通过嵌入结构体的名称访问内部字段。

示例:

type Base struct { Name string } type Child struct { Base // embedding Name string // 相同名称的字段 } c := Child{Base: Base{Name: "Base"}, Name: "Child"} fmt.Println(c.Name) // Child fmt.Println(c.Base.Name) // Base

由于对主题细节的不理解而导致的实际错误示例


故事

在项目中嵌入了标准的日志结构,然后在子类型中定义了 Logger 字段。结果,调用 d.Logger.Info() 的行为与预期不符,因为对嵌入字段的访问在同名字段之间“丢失”了。


故事

开发人员试图在子结构中重写方法,但方法无法“重写”——只是出现了一个新方法,父类的方法仍然可以通过 Child.Base.Method() 访问。这导致部分代码使用了“旧的”版本的方法,而期待不同的逻辑。


故事

在使用嵌套结构体进行 JSON 序列化时出现了意外情况:“嵌入结构体”的字段出现在根对象中。由于不知道嵌入如何影响 marshaling,结构体序列化不正确,破坏了 API 的向后兼容性。