gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-04 02:26:05 25 4
gpt4 key购买 nike

我无法理解 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)
}
这段代码的输出是 {name: John Doe, age: 35 3 Construction} .但是如果我删除 Person.String()方法定义然后输出就是 3 (它调用 engineer.TaxPayer.String() )。但是,如果我删除 TaxPayer.String()方法定义也是如此,那么输出是 {{John Doe 35} {3} Construction} .我一开始以为一定有隐含的 String()为整体定义的方法 Engineer struct,但没有这样的方法。
为什么方法调用会这样?如果我改为将每个嵌入类型的方法命名为 String() 以外的任何名称(比如 Foo() ),然后尝试做 fmt.Println(engineer.Foo()) ,我得到一个(预期的)编译错误: ambiguous selector engineer.Foo .当方法名称为 String() 时,为什么不引发此错误反而?

最佳答案

如果在结构中嵌入类型,嵌入类型的字段和方法将提升为嵌入器类型。它们的“行为”就好像它们是在嵌入器类型上定义的一样。
这是什么意思?如果输入 A嵌入型 B ,然后输入 B有一个方法 String() ,您可以调用String()类型 A (接收者仍然是 B ,这不是继承也不是虚方法)。
到现在为止还挺好。但是如果输入 A 呢?嵌入型 B并输入 C , 都有一个 String()方法?然后A.String()将是模棱两可的,因此在这种情况下 String()方法不会被推广。
这解释了你的经历。打印Engineer将具有默认格式(结构字段),因为会有 2 String()方法,所以没有用于 Engineer本身。当然,默认格式涉及打印字段,并生成默认 string值的表示,fmt包检查正在打印的值是否实现 fmt.Stringer ,如果是这样,它的 String()方法被调用。
如果删除 Person.String() ,那么只有一个 String()方法提升,来自 TaxPlayer ,因此由 fmt 调用包装生产string Engineer 的代表值(value)本身。
如果您删除 TaxPayer.String() 也是如此: 然后 Person.String()将是唯一的String()方法提升,因此用于 Engineer值(value)本身。
这在Spec: Struct types:中有详细说明

A field or method f of an embedded field in a struct x is called promoted if x.f is a legal selector that denotes that field or method f.

[...] Given a struct type S and a defined type T, promoted methods are included in the method set of the struct as follows:

  • If S contains an embedded field T, the method sets of S and *S both include promoted methods with receiver T. The method set of *S also includes promoted methods with receiver *T.
  • If S contains an embedded field *T, the method sets of S and *S both include promoted methods with receiver T or *T.

第一句说“如果 x.f 是一个合法的选择器”。法律是什么意思?
Spec: Selectors:

For a primary expression x that is not a package name, the selector expression

x.f

denotes the field or method f of the value x.

[...] A selector f may denote a field or method f of a type T, or it may refer to a field or method f of a nested embedded field of T. The number of embedded fields traversed to reach f is called its depth in T. The depth of a field or method f declared in T is zero. The depth of a field or method f declared in an embedded field A in T is the depth of f in A plus one.

The following rules apply to selectors:

  • For a value x of type T or *T where T is not a pointer or interface type, x.f denotes the field or method at the shallowest depth in T where there is such an f. If there is not exactly one f with shallowest depth, the selector expression is illegal.
  • [...]

强调了本质,它解释了为什么 String() 都没有。首先调用方法: Engineer.String()可能来自 2 个“来源”: Person.StringTaxPayer.String ,因此 Engineer.String是一个非法选择器,因此没有 String()方法将成为 Engineer 方法集的一部分.
使用非法选择器会引发编译时错误(例如“模棱两可的选择器engineer.Foo”)。所以你得到错误是因为你明确地试图引用 engineer.Foo .但只需嵌入两种类型都具有 String() ,这不是编译时错误。嵌入本身不是错误。使用非法选择器将是错误的。如果你写 engineer.String() ,这将再次引发编译时错误。但如果你只是通过 engineer打印: fmt.Println(engineer) ,这里没有非法选择器,你不引用 engineer.String() .这是允许的。 (当然,由于 Engineer 的方法集没有提升的 String() 方法,它不会被调用来为 Engineer 生成字符串表示——仅在打印字段时。)

关于go - Go 中嵌入类型的 String() 方法的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67519749/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com