2013-10-07 31 views
5

在下面的代码片段中,我想知道当其内容仍未初始化时,究竟存储在iPerson中的数据是什么:只有0字节的值?或者它实际上是一个隐藏的指针(当然也初始化为0字节)?无论如何,在iPerson = person究竟发生了什么?Go中的接口变量究竟如何实现?

如果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?的确切事实的正确性有第二个想法的结果,所以我决定尝试做一些调查,了解它是如何正是接口变量和任务对他们在工作围棋)

编辑:收到了一些有用的答案后,我还是不解与此:

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

因此,看起来像内部接口变量确实持有指向它的指针。从http://research.swtch.com/interfaces的摘录:

点的实际数据接口值的第二个字,在这种情况下b副本。作业var s Stringer = b作出b的副本,而不是指向b,原因与var c uint64 = b作出副本相同:如果b后来更改,sc应该具有原始值,而不是新值。

我的问题

[...]然后会发生什么,当执行IPerson但具有不同的尺寸/内存占用的对象被分配到iPerson?

...也被在文章中说:

存储在接口

值可能是任意大,但只有一个字是专门为持有界面结构的值,因此分配分配的内存块堆上和记录单字插槽中的指针。

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

(感谢Volker提供的链接;但是,他的答案的第一部分是事实上明显错误的......所以我不知道我是否应该为误导性信息倒下投票或为upvote for non-misleading和相当有用的链接(这也恰好违背了自己的答案))

4

当您执行以下行:

iPerson = person 

你是存储在接口变量Person值。由于赋值给一个结构体执行拷贝,因此你的代码正在拷贝。从界面内检索结构,你需要采取另一种副本:

p := iPerson.(Person) 

所以你很少想与可变类型做到这一点。如果你不是想要一个指针存储在接口变量的结构,你需要明确地做到这一点:

iPerson = &person 

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

+0

让我感到困惑的是,为什么Go允许'iPerson =&person'以及'iPerson = person'而不必改变'iPerson'的类型。它允许运行时类型的错误,而不是静态捕获。 Volker指出的这篇文章并没有提到,甚至没有提到。 –

+0

如果您将结构视为不可变的值,那么按值传递它可能会很有意义。在实践中,如果你定义了接收指针的方法,你可能不会混淆它们,因为这些方法将无法访问接口值中保存的值,所以不会混淆:http:// play .golang。org/p/E_8WjLS4S0 –

+0

好的,你说在实践中这不会成为问题吗?但我仍然认为这是语言设计中的不雅,类型系统可以在这里做得更好。 –

4

你问为什么两者

iPerson = person 
iPerson = &person 

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

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

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