理解结构体嵌套

huangapple go评论92阅读模式
英文:

Understanding struct embedding

问题

有人可以解释一下为什么这段代码打印的是1而不是2吗?

package main

import (
	"fmt"
)

type S1 struct{
	f1 string
}

type S2 struct{
	S1
	f2 string
}	

func (s *S1) Say(){
	fmt.Println("1")
}	

func (s *S2) Say(){
	fmt.Println("2")
}		

type S3 S2
		
func main() {
	var s3 S3
	s3.Say()
}

(可在此处运行:https://play.golang.org/p/_cjNxBKgSf)

英文:

Can someone explain me why this code prints 1 and not 2?

package main

import (
	"fmt"
)

type S1 struct{
	f1 string
}

type S2 struct{
	S1
	f2 string
}	
	
func (s *S1) Say(){
	fmt.Println("1")
}	
	
func (s *S2) Say(){
	fmt.Println("2")
}		

type S3 S2
		
func main() {
	var s3 S3
	s3.Say()
}

(Runnable at: https://play.golang.org/p/_cjNxBKgSf)

答案1

得分: 12

请看这个答案

具体来说,根据Go规范,我们有方法集:

方法集

一个类型可以有与之关联的方法集。接口类型的方法集就是它自己。其他任何类型T的方法集由所有声明了接收器类型为T的方法组成。对应的指针类型T的方法集由所有声明了接收器类型为T或T的方法组成(也就是说,它还包含了T的方法集)。对于包含嵌入字段的结构体,还有其他规则,详见结构体类型的章节。其他任何类型的方法集都是空的。在方法集中,每个方法必须有一个唯一的非空方法名。

然后是结构体类型:

结构体类型

结构体是一系列具有名称和类型的命名元素,称为字段。字段名可以显式指定(IdentifierList)或隐式指定(EmbeddedField)。在结构体内部,非空字段名必须是唯一的。

然后是这个:

声明了类型但没有显式字段名的字段称为嵌入字段

最后是这个:

结构体x中的嵌入字段的字段或方法f被称为提升,如果x.f是一个合法的选择器,表示该字段或方法f

提升的字段在结构体中的行为类似于普通字段,只是它们不能在结构体的复合字面值中用作字段名。

给定一个结构体类型S和一个名为T的类型,提升的方法被包含在结构体的方法集中,如下所示:

如果S包含一个嵌入字段T,则S和*S的方法集都包括具有接收器T的提升方法。*S的方法集还包括具有接收器*T的提升方法。

如果S包含一个嵌入字段*T,则S和*S的方法集都包括具有接收器T或*T的提升方法。

所有这些如何结合在一起?

你有一个:

type S2 struct{
    S1
    f2 string
}

这使得S1成为一个嵌入字段,并使得S1.Say可见。

然后你有:

type S3 S2

这使得S3具有与S2相同的内存布局和字段,但不会创建类型等价关系。这并不是说S3 "是一个" S2,而是说S3 不是与S2相同,但它们具有相同的布局。

该布局包括嵌入字段,这恰好将S1.Say引入了问题。

换句话说,类型S2具有以下基础类型:

struct { S1; f2 string }

和一个名为Say的方法。

类型S3具有相同的基础类型:

struct { S1; f2 string }

但S3和S2不同,因此S3不会从S2 "继承" 任何方法。相反,S3仅继承其基础类型的字段/方法,即f2和S1.*(包括"Say")。

英文:

See this answer.

Specifically, from the Go spec we have Method Sets:

> Method sets
>
> A type may have a method set associated with it. The method set of an
> interface type is its interface. The method set of any other type T
> consists of all methods declared with receiver type T. The method set
> of the corresponding pointer type *T is the set of all methods
> declared with receiver *T or T (that is, it also contains the method
> set of T). Further rules apply to structs containing embedded fields,
> as described in the section on struct types. Any other type has an
> empty method set. In a method set, each method must have a unique
> non-blank method name.

Then Struct typess:

> Struct types
>
> A struct is a sequence of named elements, called fields, each of which
> has a name and a type. Field names may be specified explicitly
> (IdentifierList) or implicitly (EmbeddedField). Within a struct,
> non-blank field names must be unique.

Then this:

> A field declared with a type but no explicit field name is called an embedded field.

Finally, this:

> 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.
>
> Promoted fields act like ordinary fields of a struct except that they
> cannot be used as field names in composite literals of the struct.
>
> Given a struct type S and a type named 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.

How does all that combine?

You have

type S2 struct{
    S1
    f2 string
}

which makes S1 an embedded field, and makes S1.Say visible.

Then you have:

type S3 S2

Which makes S3 have the same memory layout and fields as S2, but does not create a type equivalence. This is not saying that S3 "is a" S2, but rather that S3 is not the same as S2, but they do have the same layout.

That layout includes embedded fields, which happens to bring S1.Say into the equation.

Put another way, type S2 has an underlying type of:

struct { S1; f2 string }

and a method called Say.

Type S3 has an identical underlying type of:

struct { S1; f2 string }

But S3 and S2 are not the same, and so S3 does not "inherit" any methods from S2. Instead, S3 inherits only the fields/methods from its underlying type, which are f2, and S1.* (including "Say").

答案2

得分: 2

重要的是要知道,当你为一个类型创建另一个名称时,你不能将这些类型互换使用。它们在Go的类型系统中是两种不同的类型,尽管它们共享相同的底层表示。

你有两种不同的类型,S2S3S2有一个函数Say,而S3没有。但是由于S3具有与S2相同的底层结构,它确实有一个嵌入的S1,而S1具有一个函数Say,所以就会调用它。

英文:

Its important to know, that when you create another name for a type, you can not use the types interchangeably. They are two distinct types for Go's typesystem, even though they share the same underlying representation.

You have two distinct types, S2 and S3. S2 has a function Say, S3 however has not. But since S3 has the same underlying struct as S2, it does have a S1 embedded, which does have a function Say, so thats what gets called.

huangapple
  • 本文由 发表于 2017年8月27日 03:33:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/45898990.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定