這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Go中關鍵字interface被賦予了很多不同的含義。每個類型都有介面,意味著對那個類型定義了方法集合。
// 這段代碼定義了具有一個欄位和兩個方法的結構類型s。type S struct { i int }func (p *S) Get() int { return p.i }func (p *S) Put(v int) { p.i = v }// 定義介面type I interface { Get() int Put(int)}// 對於介面I,S是合法的實現,因為它定義了 I 所需的兩個方法。注意:即便是沒有明 確定義 S 實現了 I,這也是正確的。// Go 程式的特性介面值:func f(p I) {// 定義一個函數接受一個介面類型作為參數 fmt.Println(p.Get()) // p實現了介面I,必須有Get()方法 p.Put(1) // Put()方法是類似的 // 這裡的變數p儲存了介面類型的值。 }// 調用var s Sf(&s) // 因為S實現了I,可以調用f向其傳遞S類型的值的指標// 擷取 s 的地址,而不是 S 的值的原因,是因為在 s 的指標上定義了方法,參閱上面的 代碼 5.1。這並不是必須的——可以定義讓方法接受值——但是這樣的話 Put 方法就不 會像期望的那樣工作了。// 定義另外一個類型同樣實現介面I:type R struct { i int }func (p *R) Get() int { return p.i } func (p *R) Put(v int) { p.i=v }// 函數f現在可以接受類型為R或S的變數。// 假設需要在函數f中知道實際的類型。在Go中可以使用type switch得到。func f(p I) { switch t : p.(type) { // 類型判斷;在switch語句中使用(type)。檔案類型到變數t中。在switch之外使用(type)是非法的。 case *S: case *R: case S: case R: defaut: }}// 類型判斷不是唯一的運行時得到類型的方法。 為了在運行時得到類型,同樣可以使用 “comma, ok” 來判斷一個介面類型是否實現了 某個特定介面: if t, ok := p.(*S); ok { fmt.Printf("%d\n", t.Get()) }// 確定一個變數實現了某個介面,可以使用:t := p.(*S)
空介面
interface{} // 每個類型都能匹配到空介面func g(something interface{}) int { // 用空介面作為參數的函數 return something.(I).Get() // 值 something 具有 類型 interface{},這意味著方法沒有任何約束:它能包含任何類型。.(I) 是 類型 斷言,用於轉換 something 到 I 類型的介面。// 如果有這個類型,則可以調用 Get() 函 數。因此,如果建立一個 *S 類型的新變數,也可以調用 g(),因為 *S 同樣實現了空 介面。}s = new(S)fmt.Println(g(s)) // 列印0// 如果調用g()的參數沒有實現I,會在運行時得到警告,如i:=5 fmt.Println(g(i)) // 在運行時警告:panic: interface conversion: int is not main.I: missing method Get,這是沒有問題的,因為內建類型int沒有Get()方法。
方法
方法就是有接受者的函數,可以在任意類型上定義方法(除了非本地類型,包括內建類型:int類型不能有方法)。但是可以建立一個擁有方法的整數類型。
type Foo intfunc (f Foo) Emit(){ fmt.Printf("%v",f)}type Emitter interface{ Emit()}func (i int) Emit() {....} // 不能直接擴充內建類型func (a *net.AddrError) Emit(){...}// 不能直接擴充非本地類型// 可以通過建立類型的方式處理type newError net.AddrErrorfunc (a *newError) Emit(){...}//
介面定義為一個方法的集合。方法包含實際的代碼。換句話說,一個介面就是定義,而方法就是實現。因此,接受者不定定義為介面類型,這樣做會引起invalid receiver type編譯錯誤
接收者類型必須是 T 或 *T,這裡的 T 是類型名。T 叫做接收者基礎類型或 簡稱基礎類型。基礎類型一定不能使指標或介面類型,並且定義在與方法 相同的包中。
在Go中建立指向介面的指標是無意義的。實際上建立介面值的指標也是非法的。
介面名字
根據規則,單方法介面命名為方法名加上 -er 尾碼:Reader,Writer,Formatter 等。
有一堆這樣的命名,高效的反映了它們職責和包含的函數名。Read,Write,Close, Flush,String 等等有著規範的聲明和含義。為了避免混淆,除非有類似的聲明和含 義,否則不要讓方法與這些重名。相反的,如果類型實現了與眾所周知的類型相同的方 法,那麼就用相同的名字和聲明;將字串轉換方法命名為 String 而不是 ToString。
冒泡排序整型資料
func bubblesort(n []int) { for i:=0; i< len(n)-1; i++ { for j:=i+1; j< len(n); j++ { if n[j]<n[i] { n[i], n[j] = n[j], n[i] } } }}
類似的排序字串
func bubblesortString(n []string){ / .... / }
基於此,可能需要兩個函數,每個類型一個。而通過使用介面可以讓這個變得更加通用。
下面的方法不能用
func sort(i []interface{}) { // 函數將接受一個空介面的slice switch i.(type) {// 使用type switch找到輸入參數實際的類型 case string: // 排序 // .... case int: // ... } return /*....*/ //返回排序後的slice}
但是如果用sort([]int{1,4,5})調用這個函數,會失敗:cannot use i (type []int) as type []interface in function argment
這是因為Go不能(隱式)轉換為slice。
所以需要通過介面的方式建立Go形式的“通用”函數。
步驟:
1.定義一個有著若干排序相關方法的介面函數(這裡叫做Sorter)。至少需要擷取slice長度的函數,比較兩個值的函數和交換函數;
type Sorter interface { Len() int // len()作為方法 Less(i,j int) bool // p[j]<p[i]作為方法 Swap(i,j int) // p[i],p[i] = p[j],p[i]作為方法}
2.定義用於排序slice的新類型。注意定義的是slice類型;
type Xi []inttype Xs []string
3.實現Sorter介面的方法。
//整數為:func (p Xi) Len() int { return len(p) }func (p Xi) Less(i int, j int) bool { return p[j]<p[i] }func (p Xi) Swap(i int, j int) { p[i],p[j]=p[j],p[i] }// 字串的:func (p Xs) Len() int { return len(p) } func (p Xs) Less(i int, j int) bool { return p[j] < p[i] } func (p Xs) Swap(i int, j int) { p[i], p[j] = p[j], p[i]
4.編寫作用於Sorter介面的通用排序函數。
func Sort(x Sorter) {// x現在是Sorter類型; for i:=0; i<x.Len()-1; i++ { for j:=i+1; j<x.Len(); j++ { if x.Less(i, j) { x.Swap(i, j) } } }}// 現在可以像下面這樣使用通用的 Sort 函數:ints := Xi{44, 67, 3, 17, 89, 10, 73, 9, 14, 8}strings := Xs{"nut", "ape", "elephant", "zoo", "go"}Sort(ints)fmt.Printf("%v\n", ints)Sort(strings)fmt.Printf("%v\n", strings)