關於一個類型持有一個方法當中的細節,其中有一條:
對於一個命名過的具體類型T,它一些方法的接收者是類型T自己而另外一些則是T的指針。在T類型的參數上調用一個*T的方法是合法的,只要這個參數時一個變量
這僅僅是一個語法糖,編譯器隱式地獲取了它的地址。但事實上T類型的值並不擁有*T指針的方法,例如:安全
type IntSet struct { /* ... */ } func (*IntSet) String() string var _ = IntSet{}.String() // compile error: String requires *IntSet receiver /* 可是咱們能夠再一個IntSet值上調用這個方法: */ var s IntSet var _ = s.String() // OK: s is a variable and &s has a String method /* 然而,因爲只有*IntSet類型有String方法,因此也只有*IntSet類型實現了fmt.Stringer接口 */ var _ fmt.Stringer = &s var _ fmt.Stringer = s // compile error: IntSet lacks String method
就像信封封裝和隱藏信件同樣,接口類型封裝和隱藏具體類型和它的值。即便具體類型有其餘的方法也只有接口類型暴露出來的方法會被調用到:函數
os.Stdout.Write([]byte("hello")) // OK: *os.File has Write method os.Stdout.Close() // OK: *os.File has Close method var w io.Writer w = os.Stdout w.Write([]byte("hello")) // OK: io.Writer has Write method w.Close() // compile error: io.Writer lacks Close method
相對於少一些方法的接口,更多方法的接口類型會告訴咱們更多關於它持有的信息,而且對實現它的類的要求更加嚴格。至於被稱爲空接口類型的 interface{} ,空接口類型對實現它的類型沒有要求,因此能夠將任意一個值賦給空接口類型。ui
接口值
概念上講一個接口的值,接口值,由兩部分組成:一個具體類型和那個類型的值。它們被稱爲接口的動態類型和動態值。
對於像Go語言這種靜態類型的語言,類型是編譯器的概念:所以一個類型不是一個值。
在咱們的概念模型中,一些提供每一個類型信息的值被稱爲類型描述符,好比類型的名稱和方法。 在一個接口值中,類型部分表明與之相關類型的描述符。指針
// 下面4個語句中,變量w獲得了3個不一樣的值。(開始和最後的值是相同的。 // 在Go語言中,變量老是被一個定義明確的值初始化,即便接口類型也不例外。 // 對於一個接口的零值就是它的類型和值的部分都是nil。 // 一個接口值基於它的動態類型被描述爲空或非空,因此這是一個空的接口值。 // 能夠經過 w == nil 或者 w != nil 來判斷接口值是否爲空。 // 調用一個空接口值上的任意方法都會產生 panic。 var w io.Writer // 定義了變量w w.Write([]byte("hello")) // panic: nil pointer dereference // 這個賦值過程調用了一個具體類型到接口類型的隱式轉換,這和顯式使用io.Writer(os.Stdout)是等價的。 // 這類轉換不論是顯式的仍是隱式的,都會刻畫出操做到的類型和值。 // 這個接口值的動態類型被設爲 *os.Stdout 指針的類型描述符,它的動態值持有 os.Stdout 的拷貝。 w = os.Stdout // 將一個*os.File類型的值賦給變量w // 調用一個包含*os.File類型指針的接口值的Write方法,使得*(os.File).Write 方法被調用。 w.Write([]byte("hello")) // "hello" w = new(bytes.Buffer) //給接口值賦了一個 *bytes.Buffer 類型的值 // 如今動態類型是 *bytes.Buffer 而且動態值是一個指向新分配的緩衝區的指針 // 此次的類型描述符是 *bytes.Buffer,因此調用了(*bytes.Buffer).Write方法,而且接收者是該緩衝區的地址。 // 這個調用把字符串"hello"添加到緩衝區中。 w.Write([]byte("hello")) // writes "hello" to the bytes.Buffers w = nil //將 nil 賦給了接口值 // 這個重置將它全部的部分都設爲nil值,把變量w恢復到和它以前定義是相同的狀態。
接口值的比較
接口值可使用 == 和 != 來進行比較。兩個接口值相等僅當他們都是 nil 值或者它們的動態類型相同而且動態值也根據這個動態類型的 == 操做相等。
由於接口值是可比較的,因此它們能夠用在 map 的鍵或者做爲 switch 語句的操做數。調試
然而,若是兩個接口值的動態類型相同,但這個動態類型是不可比較的(好比切片),將它們進行比較就會失敗而且panic:code
var x interface{} = []int{1,2,3} fmt.Println(x == x) // panic: comparing uncomparable type []int
考慮到這點,接口類型是很是不同凡響的。其餘類型要麼是安全的可比較類型(如基本類型和指針),要麼是徹底不可比較的類型(如切片,映射類型,和函數),可是在比較接口值或者包含了接口值的聚合類型時,就必需要意識到潛在的panic。一樣地風險也存在於使用接口做爲map的鍵或者switch的操做數。只能比較你很是肯定它們的動態值是可比較類型的接口值。接口
當咱們處理錯誤或者調試的過程當中,得知接口值的動態類型是很是有幫助的。因此咱們使用fmt包的%T動做:字符串
var w io.Writer fmt.Printf("%T\n", w) // "<nil>" w = os.Stdout fmt.Printf("%T\n", w) // "*os.File" w = new(bytes.Buffer) fmt.Printf("%T\n", w) // "*bytes.Buffer"