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

时间:2013-10-07 10:12:59

标签: pointers interface go implementation

在下面的代码片段中,我想了解当{I}内容仍然未初始化时,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
}

(这个问题是我对why runtime error on io.WriterString?的答案的确切事实正确性的第二个想法的结果。所以我决定尝试做一些调查,以了解它是如何确切地说接口变量和赋值他们在Go工作。)

编辑:在收到一些有用的答案之后,我仍然对此感到困惑:

iPerson = person
iPerson = &person

- 两者都是合法的。但是,对我来说,这就提出了为什么编译器允许这种弱类型发生的问题?上述的一个含义是:

iPerson = &person
var person2 = iPerson.(Person)  # panic: interface conversion: interface is *main.Person, not main.Person

而更改第一行则修复了它:

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

...因此无法静态确定iPerson是否包含指针或值;并且看起来任何事情都可以在运行时为其分配任何一个而不会引发错误。为什么做出这样的设计决定?它有什么用途?它绝对不符合“类型安全”的心态。

3 个答案:

答案 0 :(得分:5)

你问为什么两个

iPerson = person
iPerson = &person

是允许的。它们都被允许,因为人和人都实现了IPerson接口。这很明显,因为IPerson是空接口 - 每个值都实现它。

确实,您无法静态确定IPerson的值是否包含指针或值。所以呢?您对IPerson的所有了解都是存储在该类型值中的任何对象都实现了接口中的方法列表。假设这些方法是正确实现的。 IPerson是否持有值或指针与此无关。

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

答案 1 :(得分:4)

执行以下行时:

iPerson = person

您在接口变量中存储Person值。由于对结构的赋值执行副本,是的,您的代码正在复制。要从界面中检索结构,您需要另外复制一份:

p := iPerson.(Person)

所以你很少想用可变类型来做这件事。如果您想要在接口变量中存储指向struct的指针,则需要明确地执行此操作:

iPerson = &person

就发生了什么而言,你是对的,接口变量分配堆空间来存储大于指针的值,但这通常对用户不可见。

答案 2 :(得分:3)

因此,在内部看来,接口变量确实包含指向分配给它的指针。摘自http://research.swtch.com/interfaces

  

界面值中的第二个单词指向实际数据,在本例中为 b的副本。作业var s Stringer = b复制b而不是指向b,原因与var c uint64 = b复制相同:如果b稍后更改,{{{ 1}}和s应该具有原始值,而不是新值。

我的问题

  

[...]当实现IPerson但具有不同大小/内存占用的对象被分配给iPerson时会发生什么?

......在文章中也得到了解答:

  

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

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

(感谢Volker提供的链接;但是,他的回答的第一部分实际上是完全错误的...所以我不知道我是否应该为误导性信息投票或为非误导性信息提供支持相当有用的链接(这也恰好与他自己的答案相矛盾)。)