這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
54.蛤蟆筆記go語言——interface使用
Go語言中使用interface是比較困難的。使用基本比較簡單,但是設計自己的interface就比較困難了。所以如何高效使用interface很有必要。
什麼是interface
一個interface包含兩個東西:一組方法(也是類型),或類型。
例如一個animal 類型可以是一個介面。可以定義animal為任何可以說話的。
type Animal interface {
Speak() string
}
這樣定義了animal,可以是任何包含speak方法的類型。
Speak沒有任何參數,返回一個字串。任何定義了該方法的類型都滿足animal介面。
沒有關鍵字來指定類型是否滿足介面,這個是自動實現的。建立一對類型來滿足這個介面。
type Dog struct {
}
func (d Dog) Speak() string {
return"Woof!"
}
type Cat struct {
}
func (c Cat) Speak() string {
return"Meow!"
}
type Llama struct {
}
func (l Llama) Speak() string {
return"?????"
}
type JavaProgrammer struct {
}
func (j JavaProgrammer) Speak() string {
return"Design patterns!"
}
這樣有4個類型的animals:一個狗、一個貓、一個llama和一個java程式員。
主函數如下:
func main() {
animals :=[]Animal{Dog{}, Cat{}, Llama{}, JavaProgrammer{}}
for _, animal:= range animals {
fmt.Println(animal.Speak())
}
}
運行如下:
Woof!
Meow!
?????
Designpatterns!
Interface{}類型
Interface{}類型是空的介面。很多疑惑的根源。
空的介面沒有方法。因為沒有implemets關鍵字,所以至少有0個方法的類型都自動滿足空介面。PS:是至少有0個,呵呵,就是任何時候都成立了哦。
意味著,如果寫了一個函數將interface{}作為介面,那麼這個函數可以接受任何值
如下函數:
func DoSomething(v interface{}) {
// ...
}
接受任何參數。
那麼問題來了,在函數體中v是什麼類型呢?是不是任何類型呢?這是不對的,v不是任何類型,而是interface{}類型。任何值在運行時候都有確定的類型,v就是interface{}類型。
其實一個interface{}值儲存的2個資料位元組。一個用於指向類型的方法表,另一個指向這個值實際儲存的資料。如果理解interface值是2個位元組包含指標指向資料,就會避免很多陷阱。對於interface介面的實現,可以參考後面的友情連結。
在上一個例子中構建animal類型的slice,不需要使用Animal(Dog{})說明Dog類型,在animal的slice中,每個元素都是animal類型,但是不同的值有不同的類型。
不知道interface如何在記憶體中儲存的確會迷惑。例如,可以將[]T轉換成[]interface{}嗎?
如果知道interface如何儲存就很容易理解了。
代碼
package main
import (
"fmt"
)
func PrintAll(vals[]interface{}) {
for _, val := range vals {
fmt.Println(val)
}
}
func main() {
names := []string{"stanley","david", "oscar"}
PrintAll(names)
}
運行報錯如下:
cannotuse names (type []string) as type []interface {} in argument to PrintAll
不能把[]string轉換為[]interface.
如果想要工作,需要將[]string轉換為[]interface{}
如下:
packagemain
import(
"fmt"
)
funcPrintAll(vals[]interface{}){
for_,val:=rangevals{
fmt.Println(val)
}
}
funcmain(){
names:=[]string{"stanley","david","oscar"}
vals:=make([]interface{},len(names))
fori,v:=rangenames{
vals[i]=v
}
PrintAll(vals)
}
執行如下:
stanley
david
oscar
這個的確不是很完美,但是實際上[]interface{}很少使用。
指標和介面
另一個介面的細節是介面定義。定義沒有描述是否使用指標接受還是值接受來實現介面。當給出的是一個介面值,不能保證類型是不是一個指標。在之前的例子中,定義所有的方法是值接收,賦值給animal的sclie. 我們來改變一下貓的Speak()方法為指標接收如下:
func (c *Cat) Speak() string {
return "Meow!"
}
如果允許就會報錯如下:
cannot use Cat literal (typeCat) as type Animal in array or slice literal:
Catdoes not implement Animal (Speak method has pointer receiver)
可以通過將*Cat指標指向animal slice來替代 Cat值。還用new(Cat)來代替Cat{}
如:
animals := []Animal{Dog{}, new(Cat), Llama{},JavaProgrammer{}}
OK,繼續。
傳遞*Dog指標代替Dog值,但是不改變Dog的Speak函數。
如下:
animals := []Animal{new(Dog), new(Cat), Llama{},JavaProgrammer{}}
也可以正常工作。但是小許不同的是,不需要改變Speak方法的接收類型。這是因為指標類型可以方法相關的方法,但是反過來是不可以的。*Dog可以使用Speak方法,但是一個Cat不能訪問*Cat的Speak.
我們要切記的是: Go中傳遞的任何東西都是值。每次你調用一個函數,會傳遞資料的副本。
func (t T)MyMethod(s string) {
// ...
}
函數類型是func(T,string),函數值通過值來傳遞。
在方法定義的接收器實值型別的改變不會被調用者看見,因為調用者是看見完整隔離的Dog值。
既然所有東西都是通過值來傳遞,那麼明顯*Cat方法不能被cat值使用,任何Cat值可能有任何的*Cat指標來指向。如果想通過使用Cat值來調用一個*Cat方法,不能使用*Cat指標開始。相反,如果有一個基於Dog類型的方法,我們有一個*Dog指標,通過*Dog指標可以明確指向一個Dog值,那麼Go runtime會在需要的時候將指標關聯到Dog值。所以,給出一個*Dog值,和一個Dog類型的方法,就可以調用Dog的方法。
友情連結
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go
http://www.laktek.com/2012/02/13/learning-go-interfaces-reflections/