go語言可利用型別斷言來進行介面類型。在Go中,無論是將一個介面類型轉換成另一個介面類型,或是將一個介面轉換為另一個基本類型,都必須使用類型斷言;轉換語法有兩種「轉換後的變數:= 介面變數. (目標類型)」和「轉換後的變數, ok := 介面變數.(目標類型)」。
本教學操作環境:windows7系統、GO 1.18版本、Dell G3電腦。
在 Golang 中,將一個 介面 型別轉換成另一個介面 型,或是將一個介面轉換為另一個基本型別,都必須使用 類型斷言。
類型斷言的格式
類型斷言是一個使用在介面值上的操作。語法上它看起來像 i.(T) 被稱為斷言類型,這裡 i 表示一個介面的型別和 T 表示一個型別。一個類型斷言檢查它操作物件的動態類型是否和斷言的類型匹配。
類型斷言的基本格式如下:
t := i.(T)
其中,i 代表介面變量,T 代表轉換的目標類型,t 代表轉換後的變數。
這裡有兩種可能。第一種,如果斷言的類型 T 是具體類型,然後類型斷言檢查 i 的動態類型是否和 T 相同。如果這個檢查成功了,型別斷言的結果是 i 的動態值,當然它的型別是 T。換句話說,具體類型的類型斷言從它的操作物件中獲得具體的值。如果檢查失敗,接下來這個操作會拋出 panic。例如:
var w io.Writer w = os.Stdout f := w.(*os.File) // 成功: f == os.Stdout c := w.(*bytes.Buffer) // 死机:接口保存*os.file,而不是*bytes.buffer
第二種,如果相反斷言的類型 T 是一個介面類型,然後類型斷言檢查是否 i 的動態類型滿足 T。如果這個檢查成功了,動態值沒有取得;這個結果仍然是一個有相同型別和值部分的介面值,但是結果有型別 T。換句話說,對一個介面類型的類型斷言改變了類型的表述方式,改變了可以獲取的方法集合(通常更大),但是它保護了介面值內部的動態類型和值的部分。
在下面的第一個型別斷言後,w 和rw 都持有os.Stdout 因此它們每個有一個動態型別*os.File,但是變數w 是一個io.Writer 型別只對外公開出檔案的Write 方法,然而rw 變數也隻公開它的Read 方法。
var w io.Writer w = os.Stdout rw := w.(io.ReadWriter) // 成功:*os.file具有读写功能 w = new(ByteCounter) rw = w.(io.ReadWriter) // 死机:*字节计数器没有读取方法
如果斷言操作的物件是一個 nil 介面值,那麼不論被斷言的類型是什麼這個類型斷言都會失敗。幾乎不需要對一個更少限制性的介面類型(更少的方法集合)做斷言,因為它表現的就像賦值操作一樣,除了對於 nil 介面值的情況。
如果 i 沒有完全實作 T 介面的方法,這個語句將會觸發宕機。觸發宕機不是很友好,因此上面的語句還有一種寫法:
t,ok := i.(T)
這種寫法下,如果發生接口未實現時,將會把ok 置為false,t 置為T 類型的0值。正常實作時,ok 為 true。這裡 ok 可以被認為是:i 介面是否實作 T 類型的結果。
將介面轉換為其他接口
實作某個介面的型別同時實作了另外一個接口,此時可以在兩個接口間轉換。
鳥和豬有不同的特性,鳥可以飛,豬不能飛,但兩種動物都可以行走。如果使用結構體實現鳥和豬,讓它們具備自己特性的 Fly() 和 Walk() 方法就讓鳥和豬各自實現了飛行動物介面(Flyer)和行走動物介面(Walker)。
將鳥和豬的實例建立後,被儲存到 interface{} 類型的 map 中。 interface{} 類型表示空接口,意思是這種接口可以保存為任意類型。對保存有鳥或豬的實例的interface{} 變數進行斷言操作,如果斷言物件是斷言指定的類型,則傳回轉換為斷言物件類型的介面;如果不是指定的斷言類型時,斷言的第二個參數將返回false。
例如下面的程式碼:
var obj interface = new(bird) f, isFlyer := obj.(Flyer)
程式碼中,new(bird) 產生 *bird 類型的 bird 實例,這個實例被保存在 interface{} 類型的 obj 變數中。使用 obj.(Flyer) 類型斷言,將 obj 轉換為 Flyer 介面。 f 為轉換成功時的 Flyer 介面類型,isFlyer 表示是否轉換成功,型別就是 bool。
下面是詳細的程式碼(程式碼1):
package main import "fmt" // 定义飞行动物接口 type Flyer interface { Fly() } // 定义行走动物接口 type Walker interface { Walk() } // 定义鸟类 type bird struct { } // 实现飞行动物接口 func (b *bird) Fly() { fmt.Println("bird: fly") } // 为鸟添加Walk()方法, 实现行走动物接口 func (b *bird) Walk() { fmt.Println("bird: walk") } // 定义猪 type pig struct { } // 为猪添加Walk()方法, 实现行走动物接口 func (p *pig) Walk() { fmt.Println("pig: walk") } func main() { // 创建动物的名字到实例的映射 animals := map[string]interface{}{ "bird": new(bird), "pig": new(pig), } // 遍历映射 for name, obj := range animals { // 判断对象是否为飞行动物 f, isFlyer := obj.(Flyer) // 判断对象是否为行走动物 w, isWalker := obj.(Walker) fmt.Printf("name: %s isFlyer: %v isWalker: %v\n", name, isFlyer, isWalker) // 如果是飞行动物则调用飞行动物接口 if isFlyer { f.Fly() } // 如果是行走动物则调用行走动物接口 if isWalker { w.Walk() } } }
程式碼說明如下:
第 6 行定義了飛行動物的介面。
第 11 行定義了行走動物的介面。
第 16 和 30 行分別定義了鳥和豬兩個對象,並分別實現了飛行動物和行走動物介面。
第 41 行是一個 map,映射物件名字和物件實例,實例是鳥和豬。
第 47 行開始遍歷 map,obj 為 interface{} 介面類型。
第 50 行中,使用类型断言获得 f,类型为 Flyer 及 isFlyer 的断言成功的判定。
第 52 行中,使用类型断言获得 w,类型为 Walker 及 isWalker 的断言成功的判定。
第 57 和 62 行,根据飞行动物和行走动物两者是否断言成功,调用其接口。
代码输出如下:
将接口转换为其他类型
在代码 1 中,可以实现将接口转换为普通的指针类型。例如将 Walker 接口转换为 *pig 类型,请参考下面的代码:
p1 := new(pig) var a Walker = p1 p2 := a.(*pig) fmt.Printf("p1=%p p2=%p", p1, p2)
对代码的说明如下:
第 3 行,由于 pig 实现了 Walker 接口,因此可以被隐式转换为 Walker 接口类型保存于 a 中。
第 4 行,由于 a 中保存的本来就是 *pig 本体,因此可以转换为 *pig 类型。
第 6 行,对比发现,p1 和 p2 指针是相同的。
如果尝试将上面这段代码中的 Walker 类型的 a 转换为 *bird 类型,将会发出运行时错误,请参考下面的代码:
p1 := new(pig) var a Walker = p1 p2 := a.(*bird)
运行时报错:
panic: interface conversion: main.Walker is *main.pig, not *main.bird
报错意思是:接口转换时,main.Walker 接口的内部保存的是 *main.pig,而不是 *main.bird。
因此,接口在转换为其他类型时,接口内保存的实例对应的类型指针,必须是要转换的对应的类型指针。
总结
接口和其他类型的转换可以在Go语言中自由进行,前提是已经完全实现。
接口断言类似于流程控制中的 if。但大量类型断言出现时,应使用更为高效的类型分支 switch 特性。
以上是go語言介面類型怎麼轉換的詳細內容。更多資訊請關注PHP中文網其他相關文章!