Go 中嵌入类型的 String() 方法的奇怪行为

Sam*_*ats 3 methods struct embedding go

我无法理解 String() 方法如何用于 Go 中的嵌入式结构。考虑一下:

type Engineer struct {
    Person
    TaxPayer
    Specialization string
}

type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("name: %s, age: %d", p.Name, p.Age)
}

type TaxPayer struct {
    TaxBracket int
}

func (t TaxPayer) String() string {
    return fmt.Sprintf("%d", t.TaxBracket)
}

func main() {
    engineer := Engineer{
        Person: Person{
            Name: "John Doe",
            Age:  35,
        },
        TaxPayer: TaxPayer{3},
        Specialization: "Construction",
    }
    fmt.Println(engineer)
}
Run Code Online (Sandbox Code Playgroud)

这段代码的输出是{name: John Doe, age: 35 3 Construction}。但是如果我删除Person.String()方法定义,那么输出就是3(它调用engineer.TaxPayer.String())。但是,如果我也删除TaxPayer.String()方法定义,则输出为{{John Doe 35} {3} Construction}. 我最初认为必须String()为整个Engineer结构定义一个隐式方法,但没有这样的方法。

为什么方法调用会这样?如果我改为将每个嵌入类型的方法命名为String()(例如Foo())以外的任何名称,然后尝试执行fmt.Println(engineer.Foo()),我会收到(预期的)编译错误:ambiguous selector engineer.Foo。当方法的名称String()改为时,为什么不引发此错误?

icz*_*cza 6

如果在结构中嵌入类型,嵌入类型的字段和方法将提升为嵌入器类型。它们的“行为”就好像它们是在嵌入器类型上定义的一样。

这是什么意思?如果 typeA嵌入 type B,并且 typeB有方法String(),则可以调用String()type A(接收者仍然是B,这不是继承也不是虚方法)。

到现在为止还挺好。但是如果 typeA嵌入 typeB和 type C,两者都有一个String()方法呢?那么A.String()将是模棱两可的,因此在这种情况下String()不会提升该方法。

这解释了你的经历。打印Engineer将具有默认格式(结构字段),因为将有 2String()种方法,因此没有用于Engineer 自身。当然,默认格式涉及打印字段,并且为了生成值的默认 string表示,fmt包检查正在打印的值是否实现了fmt.Stringer,如果是,String()则调用它的方法。

如果您删除Person.String(),则只有一个String()方法被提升, from TaxPlayer,以便由fmt包调用以生成值本身的string表示Engineer

如果您删除TaxPayer.String() : thenPerson.String()将是唯一String()提升的方法,因此它用于Engineer值本身。

这在规范:结构类型中有详细说明

如果是表示该字段或方法的合法选择器,则结构中嵌入字段的字段或方法 称为提升fxx.ff

[...] 给定一个 struct typeS和一个定义的 type T,提升的方法包含在结构的方法集中,如下所示:

  • 如果S包含嵌入字段T,该方法集S*S两者都包括推进与接收器的方法T。方法集*S还包括带有接收器的提升方法*T
  • 如果S包含嵌入字段*T,则S和的方法集*S都包含带有接收者T或的提升方法*T

第一句陈述了“ifx.f是一个合法的选择器”法律是什么意思?

规格: 选择器:

对于一次式 x,是不是一个包的名称中,选择表达

x.f
Run Code Online (Sandbox Code Playgroud)

表示场或方法f的价值x

[...] 选择器f可以表示一个f类型的字段或方法T,或者它可以引用f嵌套的嵌入字段的字段或方法T。走过来达到嵌入式领域的数f称为它的深度T。中f声明的字段或方法的深度T为零。在嵌入字段A中声明的字段或方法 fT的深度是fin的深度A加一。

以下规则适用于选择器:

  • 对于xtypeT*TwhereT不是指针或接口类型的值,x.f表示最浅深度的字段或方法,T其中存在此​​类f如果不完全是f最浅深度的,则选择器表达式是非法的。
  • [...]

强调了本质,它解释了为什么首先没有String()调用任何方法:Engineer.String()可能来自 2 个“来源”:Person.String并且TaxPayer.String,因此Engineer.String是非法选择器,因此没有任何String()方法将成为 的方法集的一部分Engineer

使用非法选择器会引发编译时错误(例如“模棱两可的选择器engineer.Foo”)。所以你得到错误是因为你明确地试图引用engineer.Foo. 但只是嵌入两种类型都具有String(),这不是编译时错误。嵌入本身不是错误。使用非法选择器将是错误的。如果你写engineer.String(),那会再次引发编译时错误。但是如果你只是通过engineerprinting: fmt.Println(engineer),这里没有非法选择器,你不引用engineer.String(). 这是允许的。(当然,由于方法 set ofEngineer没有提升的String()方法,因此Engineer在打印字段时不会调用它来为–only生成字符串表示。)