這是一個建立於
的文章,其中的資訊可能已經有所發展或是發生改變。下面定義了具有一個欄位和兩個方法的結構類型S代碼6.1type S struct { i int }func (p *S) Get() int { return p.i }func (p *S) Put(v int) { p.i = v }
也可以定義介面類型,僅僅是方法的集合。這裡定義了一個有兩個方法的介面I:type I interface { Get() int Put(int)}對於介面I,S是合法的實現,因為它定義了I所需的兩個方法。注意,即便是沒有明確定義S實現了I,這也是正確的。
介面值:例:func f(p I) { //定義一個函數接受一個介面類型作為參數 fmt.Println(p.Get()) //p實現了介面I,必須有Get()方法 p.Put(1) //Put()方法是類似的}
這裡的變數p儲存了介面類型的值。因為S實現了I,可以調用f向其傳遞S類型的值的指標:var s S; f(&s)擷取s的地址,而不是S的值的原因,是因為在s的指標上定義了方法,參閱上面的代碼6.1。這並不是必須的——可以定義讓方法接受值——但是這樣的話Put方法就不會像期望的那樣工作了。實際上,無須明確一個類型是否實現了一個介面意味著Go實現了叫做duck typing的模式。這不是純粹的duck typing,因為如果可能的話Go編譯器將對類型是否實現了介面進行實現靜態檢查。假設需要在函數f中知道實際的類型。在Go中可以使用type switch得到。func f(p I) {
switch t := p.(type) { //類型判斷。在switch語句中使用(type)。檔案類型到變數t;
case *S: //p的實際類型是S的指標;
case *R: //p的實際類型是R的指標;
case S: //p的實際類型是S;
case R: //p的實際類型是R;
default: //實現了I的其他類型。
}
}
注意: element.(type)文法不能在switch外的任何邏輯裡面使用,如果你要在switch外面判斷一個類型就使用 comma-ok。
類型判斷不是唯一的運行時得到類型的方法。為了在運行時得到類型,同樣可以使用“comma, ok”來判斷一個介面類型是否實現了某個特定介面:if t, ok := something.(I); ok {// 對於某些實現了介面I 的// t 是其所擁有的類型}
Comma-ok斷言
Go語言裡面有一個文法,可以直接判斷是否是該類型的變數: value, ok = element.(T),這裡value就是變數的值,ok是一個bool類型,element是interface變數,T是斷言的類型。
如果element裡面確實儲存了T類型的數值,那麼ok返回true,否則返回false。
例:package main
import ( "fmt" "strconv")
type Element interface{}type List [] Element
type Person struct { name string age int}
//定義了String方法,實現了fmt.Stringerfunc (p Person) String() string { return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"}
func main() { list := make(List, 3) list[0] = 1 // an int list[1] = "Hello" // a string list[2] = Person{"Dennis", 70}
for index, element := range list { if value, ok := element.(int); ok { fmt.Printf("list[%d] is an int and its value is %d\n", index, value) } else if value, ok := element.(string); ok { fmt.Printf("list[%d] is a string and its value is %s\n", index, value) } else if value, ok := element.(Person); ok { fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) } else { fmt.Println("list[%d] is of a different type", index) } }}輸出結果:list[0] is an int and its value is 1list[1] is a string and its value is Hellolist[2] is a Person and its value is (name: Dennis - age: 70 years)
確定一個變數實現了某個介面,可以使用:t := something.(I)
空介面由於每個類型都能匹配到空介面:interface{}。我們可以建立一個接受空介面作為參數的普通函數:Listing 6.2.用空介面作為參數的函數func g(something interface{}) int { return something.(I).Get()}在這個函數中的return something.(I).Get()是有一點竅門的。值something具有類型interface{},這意味著方法沒有任何約束:它能包含任何類型。.(I)是類型斷言,用於轉換something到I類型的介面。如果有這個類型,則可以調用Get()函數。因此,如果建立一個*S類型的新變數,也可以調用g(),因為*S同樣實現了空介面。s = new(S)fmt.Println(g(s));例:package mainimport ( "fmt")func main(){ s := new(S) ss := "Hello world" fmt.Printf("%d\n",g(s)) fmt.Printf("%s\n",demo(ss))}func g(something interface{}) int { return
something.(I).Get() //?為什麼不是something.(S).Get()}func demo(something interface{}) string { return something.(string)}type I interface { Get() int Put(int)}type S struct { i int }func (p *S) Get() int { return p.i}func (p *S) Put(v int) { p.i = v}
輸出結果:
0Hello world
例2:package mainimport ( "fmt")func main(){ s := S{1} fmt.Printf("%d\n",g(s))}func g(something S) int { return something.Get()}type S struct { i int }func (p *S) Get() int { return p.i}func (p *S) Put(v int) { p.i = v}
輸出結果:
1
介面名字根據規則,單方法介面命名為方法名加上-er尾碼:Reader,Writer,Formatter等。有一堆這樣的命名,高效的反映了它們職責和包含的函數名。Read,Write,Close,Flush,String等等有著規範的聲明和含義。為了避免混淆,除非有類似的聲明和含義,否則不要讓方法與這些重名。相反的,如果類型實現了與眾所周知的類型相同的方法,那麼就用相同的名字和聲明;將字串轉換方法命名為String而不是ToString。