Go 中 String() 方法在嵌入式类型上的奇怪行为
Weird Behavior of String() Method on Embedded Types in 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)
}
这段代码的输出是{name: John Doe, age: 35 3 Construction}
。但是,如果我删除 Person.String()
方法定义,则输出只是 3
(它调用 engineer.TaxPayer.String()
)。但是,如果我也删除 TaxPayer.String()
方法定义,则输出为 {{John Doe 35} {3} Construction}
。我最初认为必须为整个 Engineer
结构定义一个隐式 String()
方法,但没有这样的方法。
为什么方法调用会这样?如果我改为将每个嵌入类型的方法命名为 String()
以外的任何名称(例如 Foo()
),然后尝试执行 fmt.Println(engineer.Foo())
,我会得到一个(预期的)编译错误:ambiguous selector engineer.Foo
。为什么当方法名称为 String()
时不会引发此错误?
如果在结构中嵌入类型,嵌入类型的字段和方法将提升为嵌入类型。它们的“行为”就好像它们是在嵌入器类型上定义的一样。
这是什么意思?如果类型 A
嵌入了类型 B
,并且类型 B
有一个方法 String()
,您可以在类型 A
上调用 String()
(接收者将仍然是B
,这不是继承也不是虚方法)。
到目前为止一切顺利。但是如果类型 A
嵌入类型 B
和类型 C
,两者都有一个 String()
方法呢?那么 A.String()
将是不明确的,因此在这种情况下 String()
方法将不会被提升。
这解释了你的经历。打印 Engineer
将具有默认格式(结构字段),因为会有 2 个 String()
方法,因此 none 用于 Engineer
本身 .当然,默认格式涉及打印字段,并且要生成值的 default string
表示,fmt
包检查打印的值是否实现 fmt.Stringer
,如果是,则调用其 String()
方法。
如果您删除 Person.String()
,那么只有一个 String()
方法从 TaxPlayer
中提升,因此 fmt
包会调用它来生成string
Engineer
值本身的表示。
如果您删除 TaxPayer.String()
也是如此:那么 Person.String()
将是唯一提升的 String()
方法,因此它用于 Engineer
值本身。
中有详细说明
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
是一个合法的选择器”。 合法是什么意思?
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()
方法中的 none:Engineer.String()
可能来自 2 个“来源”:Person.String
和 TaxPayer.String
,因此 Engineer.String
是非法选择器,因此 String()
方法中的 none 将成为 Engineer
方法集的一部分。
使用非法选择器会引发编译时错误(例如 "ambiguous selector engineer.Foo")。所以你得到错误是因为你 明确地 试图引用 engineer.Foo
。但是仅仅嵌入两种类型都具有 String()
,这不是编译时错误。嵌入本身不是错误。使用非法选择器将是错误的。如果你写 engineer.String()
,那会再次引发编译时错误。但是如果你只是通过engineer
来打印:fmt.Println(engineer)
,这里没有非法选择器,你不引用engineer.String()
。这是允许的。 (当然,由于 Engineer
的方法集没有提升的 String()
方法,因此不会调用它来为 Engineer
生成字符串表示形式——仅在打印字段时。)
我无法理解 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}
。我最初认为必须为整个 Engineer
结构定义一个隐式 String()
方法,但没有这样的方法。
为什么方法调用会这样?如果我改为将每个嵌入类型的方法命名为 String()
以外的任何名称(例如 Foo()
),然后尝试执行 fmt.Println(engineer.Foo())
,我会得到一个(预期的)编译错误:ambiguous selector engineer.Foo
。为什么当方法名称为 String()
时不会引发此错误?
如果在结构中嵌入类型,嵌入类型的字段和方法将提升为嵌入类型。它们的“行为”就好像它们是在嵌入器类型上定义的一样。
这是什么意思?如果类型 A
嵌入了类型 B
,并且类型 B
有一个方法 String()
,您可以在类型 A
上调用 String()
(接收者将仍然是B
,这不是继承也不是虚方法)。
到目前为止一切顺利。但是如果类型 A
嵌入类型 B
和类型 C
,两者都有一个 String()
方法呢?那么 A.String()
将是不明确的,因此在这种情况下 String()
方法将不会被提升。
这解释了你的经历。打印 Engineer
将具有默认格式(结构字段),因为会有 2 个 String()
方法,因此 none 用于 Engineer
本身 .当然,默认格式涉及打印字段,并且要生成值的 default string
表示,fmt
包检查打印的值是否实现 fmt.Stringer
,如果是,则调用其 String()
方法。
如果您删除 Person.String()
,那么只有一个 String()
方法从 TaxPlayer
中提升,因此 fmt
包会调用它来生成string
Engineer
值本身的表示。
如果您删除 TaxPayer.String()
也是如此:那么 Person.String()
将是唯一提升的 String()
方法,因此它用于 Engineer
值本身。
A field or method
f
of an embedded field in a structx
is called promoted ifx.f
is a legal selector that denotes that field or methodf
.[...] Given a struct type
S
and a defined typeT
, promoted methods are included in the method set of the struct as follows:
- If
S
contains an embedded fieldT
, the method sets ofS
and*S
both include promoted methods with receiverT
. The method set of*S
also includes promoted methods with receiver*T
.- If
S
contains an embedded field*T
, the method sets ofS
and*S
both include promoted methods with receiverT
or*T
.
第一句陈述“如果x.f
是一个合法的选择器”。 合法是什么意思?
For a primary expression
x
that is not a package name, the selector expressionx.f
denotes the field or method
f
of the valuex
.[...] A selector
f
may denote a field or methodf
of a typeT
, or it may refer to a field or methodf
of a nested embedded field ofT
. The number of embedded fields traversed to reachf
is called its depth inT
. The depth of a field or methodf
declared inT
is zero. The depth of a field or method f declared in an embedded fieldA
inT
is the depth off
inA
plus one.The following rules apply to selectors:
- For a value
x
of typeT
or*T
whereT
is not a pointer or interface type,x.f
denotes the field or method at the shallowest depth inT
where there is such anf
. If there is not exactly onef
with shallowest depth, the selector expression is illegal.- [...]
强调了本质,它解释了为什么首先调用 String()
方法中的 none:Engineer.String()
可能来自 2 个“来源”:Person.String
和 TaxPayer.String
,因此 Engineer.String
是非法选择器,因此 String()
方法中的 none 将成为 Engineer
方法集的一部分。
使用非法选择器会引发编译时错误(例如 "ambiguous selector engineer.Foo")。所以你得到错误是因为你 明确地 试图引用 engineer.Foo
。但是仅仅嵌入两种类型都具有 String()
,这不是编译时错误。嵌入本身不是错误。使用非法选择器将是错误的。如果你写 engineer.String()
,那会再次引发编译时错误。但是如果你只是通过engineer
来打印:fmt.Println(engineer)
,这里没有非法选择器,你不引用engineer.String()
。这是允许的。 (当然,由于 Engineer
的方法集没有提升的 String()
方法,因此不会调用它来为 Engineer
生成字符串表示形式——仅在打印字段时。)