? 個人博客:https://blog.csdn.net/Newin2020?type=blog
📝 專欄地址:https://blog.csdn.net/newin2020/category_12898955.html
📣 專欄定位:為 0 基礎剛入門 Golang 的小伙伴提供詳細的講解,也歡迎大佬們一起交流~
📚 專欄簡介:在這個專欄,我將帶著大家從 0 開始入門 Golang 的學習。在這個 Golang 的新人系列專欄下,將會總結 Golang 入門基礎的一些知識點,并由淺入深的學習這些知識點,方便大家快速入門學習~
?? 如果有收獲的話,歡迎點贊 👍 收藏 📁 關注,您的支持就是我創作的最大動力 💪
1. 快速了解
在 Go 語言(Golang)中,斷言(Type Assertion)是一種用于在運行時檢查接口值實際保存的具體類型,并獲取對應類型值的機制。
例如,下面 add 函數中入參被定義成了空接口的類型,因此我們可以針對入參進行類型斷言,通過 a.(int) 的方式來判斷入參的類型是否為 int 類型。
func add(a, b interface{}) interface{} {ai, ok := a.(int)if !ok {panic("not int type")}bi, _ := b.(int)return ai + bi
}func main() {a := 1b := 2fmt.Println(add(a,b))
}
當然,我們可以利用 switch 語法來適應不同類型的斷言:
func add(a, b interface{}) interface{} {switch a.(type) {case int :ai, _ := a.(int)bi, _ := b.(int)return ai + bicase int32:ai, _ := a.(int32)bi, _ := b.(int32)return ai + bicase float64:af, _ := a.(float64)bf, _ := b.(float64)return af + bfdefault:panic("not supported type")}
}func main() {fmt.Println(add(1,2))fmt.Println(add(1.2,2.3))
}
而從前面的接口篇章可以得知,接口可以分為「空接口」和「非空接口」兩類。
相對于接口這種「抽象類型」,int、string、slice 等等都可以被稱為「具體類型」。
- 類型斷言作用于接口值之上,可以是空接口或非空接口
- 斷言的目標類型,可以是具體類型或非空接口類型
這樣,就組合出了四種類型斷言,接下來我們就逐一看看它們究竟是怎樣 “斷言” 的。
2 空接口.(具體類型)
var e interface {}r, ok := e.(*os.File)
上面的代碼中 e.(*os.File) 是要判斷 e 的動態類型是否為 *os.File,而這只需要確定 _type 是否指向 *os.File 的類型元數據即可。
在 go 語言里每種類型的類型元數據都是全局唯一的,如果像下面這樣給 e 賦值,那么 e 的動態值就是 f,動態類型就是 *os.File。
所以下面代碼會斷言成功,ok 為 true,r 被賦值為 e 的動態值。
var e interface {}f, _ := os.Open("output.txt")
e = fr, ok := e.(*os.File)
而如果像下面這樣賦值,e 的動態類型就是 string 類型,那么類型斷言就會失敗。
此時 ok 就為 false,r 會被賦值為 *os.File 的類型零值 nil。
var e interface {}f := "output"
e = fr, ok := e.(*os.File)
3 非空接口.(具體類型)
var rw io.ReadWriterr, ok := rw.(*os.File)
上面代碼中 rw.(*os.File) 是要判斷 rw 的動態類型是否為 *os.File。
之前的篇章中,我們也介紹過,程序中用到的 itab 結構體都會緩存起來,可以通過接口類型和動態類型組合起來的 key,查找到對應的 itab 指針。
所以這里的類型斷言只需要一次比較就能完成,只要看 tab = &itab 是否指向這個 itab 結構體即可
如果 rw 像下面代碼這樣賦值,那么它的動態值就是 f,動態類型就是 *os.File。
var rw io.ReadWriterf, _ := os.Open("output.txt")
rw = fr, ok := rw.(*os.File)
所以 tab 指向下面圖中第二塊部分的這個 itab 結構體,因此類型斷言成功,ok 為 true 且 r 被賦值為 rw 的動態值。
然而,如果 rw 動態類型不是 *os.File。
var rw io.ReadWriterf, _ := output{name: "output"}
rw = fr, ok := rw.(*os.File)
那么此時 tab 就指向下面圖中第一塊部分,而不是圖中第二塊部分的地方。所以類型斷言就會失敗,ok 為 false 且 r 會被置為 *os.File 的類型零值 nil。
4. 空接口.(非空接口)
var e interface {}rw, ok := e.(ioReadWriter)
e.(ioReadWriter) 是要判斷 e 的動態類型是否實現了 io.ReadWriter 接口,當然之前的篇章中我們已經介紹過類型關聯的方法列表該去哪里找了。
如果像下面代碼中 e 這樣賦值,那么它的動態值就是 f,動態類型就是 *os.File。
var e interface {}f, _ := os.Open("output.txt")
e = frw, ok := e.(ioReadWriter)
雖然 *os.File 元數據后面可以找到類型關聯的方法元數據數組,但也不必每次都去檢查這里是否有對應接口要求的所有方法。
因為存在 itab 緩存,所以可以先去 itab 緩存中查找一下。若沒有 io.ReadWriter 和 *os.File 對應的 itab 結構體,再去檢查 *os.File 的方法列表也不遲。
值得強調的是,就算能夠從緩存中查找到對應的 itab 指針,也要進一步判斷 itab.fun[0] 是否等于 0。
這是因為斷言失敗的類型組合,其對應的 itab 結構體也會被緩存起來,只是會把 itab.fun[0] 置為 0,用以表示這里的動態類型并沒有實現對應的接口。
這樣以后再遇到同種類型斷言時,就不用再去檢查方法列表了,可以直接斷言失敗。
而我們上面這個例子中,類型斷言是成功的,所以 ok 為 true,而 rw 就是一個 io.ReadWriter 類型的變量,其動態值與 e 相同,tab 就指向下面這樣一個 itab 結構體。
但如果 e 被賦值為一個字符串,那么它的動態類型就是 string,并沒有實現要求的 Read 和 Write 方法,所以對應的 itab 中 fun[0] 的值為 0,并且會被添加到 itab 緩存中。
var e interface {}f, _ := "output"
e = frw, ok := e.(ioReadWriter)
因此這里斷言失敗,ok 為 false 且 rw 為 io.ReadWriter 的類型零值 nil。
5. 非空接口.(非空接口)
var w io.Writerrw, ok := w.(io.ReadWriter)
w.(io.ReadWriter) 是要判斷 w 存儲的動態類型是否實現了 io.ReadWriter 接口,w 是 io.Writer 類型,接口要求一個 Write 方法,而 io.ReadWriter 接口要求實現 Read 和 Write 兩個方法。
如果 w 像下面的代碼這樣賦值,其動態值就是 f。
var w io.Writerf, _ := os.Open("output.txt")
w = frw, ok := w.(io.ReadWriter)
tab 指向下圖這樣一個 itab 結構體,要確定 *os.File 是否實現了 io.ReadWriter 接口,同樣會先去 itab 緩存里查找這個組合對應的 itab 指針。
若存在,且 itabfun[0] 不等于 0,則斷言成功;若不存在,再去檢查 *os.File 的方法列表,并緩存 itab 信息。
而在這里的代碼中,斷言是成功的,即 ok 為 true 且 rw 為 io.ReadWriter 類型的變量,動態值與 w 相同,而 tab 指向下面這個 itab 結構體。
如果我們自定義一個 output 類型,并且 *output 類型只實現了 io.Writer 接口,并沒有實現 io.ReadWriter 接口。
type output struct {name string
}func (o *output) Write(b []byte) (n int, err error) {return len(o.name), nil
}
現在如果把這個 output 類型的變量賦值給 w,那么此時 w 的動態類型就為 *output,并沒有實現指定接口,所以 fun[0] = 0,該 itab 結構體就會被緩存起來。
var w io.Writerf, _ := output{name: "john")
w = &frw, ok := w.(io.ReadWriter)
因此,類型斷言會失敗,ok 為 false 且 rw 的 tab 和 data 均為 nil。
所以,類型斷言的關鍵是明確接口的動態類型,以及對應的類型實現了哪些方法。而明確這些的關鍵還是「類型元數據」,以及空接口與非空接口的「數據結構」。