10

在下面的代码片段中,我想了解在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
}

(这个问题是我对为什么 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保存指针或值;似乎任何东西都可以在运行时为其分配任何一个而不会引发错误。为什么会做出这样的设计决定?它的用途是什么?它绝对不符合“类型安全”的心态。

4

3 回答 3

7

你问为什么两者

iPerson = person
iPerson = &person

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

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

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

于 2013-10-07T19:47:06.793 回答
6

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

接口值中的第二个字指向实际数据,在本例中是 b. 分配var s Stringer = b复制b而不是指向复制的b原因与var c uint64 = b复制的原因相同:如果b以后发生更改,s并且c应该具有原始值,而不是新值。

我的问题

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

...也在文章中得到回答:

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

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

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

于 2013-10-07T11:08:22.640 回答
5

当您执行以下行时:

iPerson = person

您正在Person接口变量中存储一个值。由于分配给结构执行副本,是的,您的代码正在复制。要从接口内部检索结构,您需要获取另一个副本:

p := iPerson.(Person)

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

iPerson = &person

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

于 2013-10-07T11:25:49.117 回答