2013-10-07 47 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是否擁有一個值或一個指針與此無關。

例如,如果該方法應該改變存儲在對象中的某些東西,那麼該方法幾乎必須是一個指針方法,在這種情況下,只有一個指針值可以存儲在接口類型的變量中。但是如果沒有一種方法改變存儲在對象中的東西,那麼它們都可以是值方法,並且非指針值可以存儲在變量中。