接口变量在Go中是如何实现的?

接口变量在Go中是如何实现的?,go,pointers,interface,compilation,Go,Pointers,Interface,Compilation,在下面的代码片段中,我想了解当iPerson的内容仍然未初始化时,它到底存储了什么:只有0字节的值?或者它实际上是一个隐藏的指针(当然也初始化为0字节)?在任何情况下,iPerson=person到底发生了什么 如果iPerson=person复制了person,那么当实现iPerson但具有不同大小/内存占用的对象被分配给iPerson时会发生什么?我知道iPerson是存储在堆栈上的变量,因此其大小必须固定。这是否意味着堆实际上是在后台使用的,因此iPerson实际上是作为指针实现的,但是赋

在下面的代码片段中,我想了解当
iPerson
的内容仍然未初始化时,它到底存储了什么:只有0字节的值?或者它实际上是一个隐藏的指针(当然也初始化为0字节)?在任何情况下,
iPerson=person
到底发生了什么

如果
iPerson=person
复制了
person
,那么当实现
iPerson
但具有不同大小/内存占用的对象被分配给
iPerson
时会发生什么?我知道
iPerson
是存储在堆栈上的变量,因此其大小必须固定。这是否意味着堆实际上是在后台使用的,因此
iPerson
实际上是作为指针实现的,但是赋值仍然复制对象,如上面的代码所示? 代码如下:

type Person struct{ name string }

type IPerson interface{}

func main() {
    var person Person = Person{"John"}
    var iPerson IPerson
    fmt.Println(person)  // => John
    fmt.Println(iPerson) // => <nil>  ...so looks like a pointer

    iPerson = person     //           ...this seems to be making a copy
    fmt.Println(iPerson) // => John

    person.name = "Mike"
    fmt.Println(person)  // => Mike
    fmt.Println(iPerson) // => John   ...so looks like it wasn't a pointer,
                         //           or at least something was definitely copied
}
-两者都是合法的。然而,对我来说,这就提出了一个问题:为什么编译器允许出现这种弱类型?上述情况的一个含义是:

iPerson = &person
var person2 = iPerson.(Person)  # panic: interface conversion: interface is *main.Person, not main.Person
而更改第一行可以修复它:

iPerson = person
var person2 = iPerson.(Person)  # OK

…因此无法静态确定
iPerson
是否持有指针或值;而且似乎任何东西都可以在运行时将其中一个分配给它,而不会产生任何错误。为什么会做出这样的设计决定?它有什么用途?这显然不符合“类型安全”的思维模式。

因此,从内部看,接口变量确实包含一个指向分配给它的内容的指针。摘自:

界面值中的第二个单词指向实际数据,在本例中为一份
b
。赋值
var s Stringer=b
复制
b
,而不是指向
b
,原因与
var c uint64=b
复制的原因相同:如果
b
以后发生变化,
s
c
应该具有原始值,而不是新值

我的问题

[…]当一个实现IPerson但大小/内存占用不同的对象被分配给IPerson时会发生什么

…这篇文章也给出了答案:

存储在接口中的值可能任意大,但只有一个字专用于保存接口结构中的值,因此分配会在堆上分配一块内存,并将指针记录在一个字槽中

是的,在堆上创建一个副本,并将指向它的指针分配给接口变量。但是,显然,对于程序员来说,接口变量具有值变量而不是指针变量的语义


(感谢沃尔克提供了链接;但是,他的答案的第一部分事实上是完全错误的……所以我不知道我是应该对误导性信息投反对票,还是应该对非误导性和相当有用的链接投赞成票(这也恰好与他自己的答案相矛盾)。

当你执行以下命令时:

iPerson = person
您正在接口变量中存储一个
Person
值。由于对结构的赋值执行复制,所以是的,您的代码正在进行复制。要从接口内部检索结构,您需要获取另一个副本:

p := iPerson.(Person)
因此,您很少希望对可变类型执行此操作。如果希望在接口变量中存储指向结构的指针,则需要显式执行此操作:

iPerson = &person
至于幕后发生的事情,接口变量分配堆空间来存储大于指针的值是正确的,但这通常对用户来说是不可见的。

您会问为什么这两个变量

iPerson = person
iPerson = &person
是允许的。它们都是允许的,因为person和&person都实现了IPerson接口。这是显而易见的,因为IPerson是空接口——每个值都实现它

确实,您不能静态地确定IPerson的值是包含指针还是值。那又怎么样?关于IPerson,您所知道的是,存储在该类型的值中的任何对象都实现接口中的方法列表。假设这些方法得到了正确的实现。IPerson是否持有值或指针与此无关


例如,如果该方法应该更改存储在对象中的某些内容,那么该方法几乎必须是指针方法,在这种情况下,接口类型的变量中只能存储指针值。但是,如果没有任何方法更改存储在对象中的内容,那么它们都可以是值方法,并且可以在变量中存储非指针值。

让我困惑的是,为什么Go既允许
iPerson=&person
也允许
iPerson=person
,而不必更改
iPerson
的类型。它允许静态捕获运行时类型的错误。这是Volker指出的文章没有涉及甚至没有提到的东西。如果你将结构视为一个不可变的值,那么按值传递它可能很有意义。在实践中,如果您将方法定义为接受指针接收器,您可能不会混淆它们,因为这些方法将无法访问接口值中的值,因此不会混淆:好的,那么您是说在实践中这不会是一个问题?但我仍然认为这是语言设计中的一个不雅之处,类型系统在这里可以做得更好。好吧,你把你的例子简化为一个空接口,它意味着接受任何定义。至于您关于类型断言中恐慌的问题,这是因为接口变量不包含
Person
值。如果改为
iPerson.(*Person)
,则可以成功检索该值。
iPerson = person
iPerson = &person