概述 由於Go語言不允許隱式類型轉換,不同的類型之間的轉換必須做顯示的類型轉換。而類型轉換和類型斷言的本質,就是把一個類型轉換到另一個類型。 不過Go語言必須做顯示的類型轉換的要求也有例外的情況: 當普通 T 類型變數向 I 介面類型轉換時,是隱式轉換的(編譯時轉換);(T->I) 當 IX 介面變 ...
概述
由於Go語言不允許隱式類型轉換,不同的類型之間的轉換必須做顯示的類型轉換。而類型轉換和類型斷言的本質,就是把一個類型轉換到另一個類型。
不過Go語言必須做顯示的類型轉換的要求也有例外的情況:
- 當普通 T 類型變數向
I
介面類型轉換時,是隱式轉換的(編譯時轉換);(T->I) - 當
IX
介面變數向
I
介面類型轉,是隱式轉換的(編譯完成時轉換的);(I->I)
類型之間轉換的例子
下麵是Go語言規範給出的部分例子:
*Point(p) // same as *(Point(p)) (*Point)(p) // p is converted to *Point <-chan int(c) // same as <-(chan int(c)) (<-chan int)(c) // c is converted to <-chan int func()(x) // function signature func() x (func())(x) // x is converted to func() (func() int)(x) // x is converted to func() int func() int(x) // x is converted to func() int (unambiguous)
簡單的說, x
需要轉換為T
類型的語法是T(x)
.
如果對於某些地方的優先順序拿不准可以自己加()
約束.
最後一個轉換就是一個容易混淆的語句, 因此需要用括弧(func() int)(x)
提高優先順序.
還有一個容易混淆的地方是 只讀和只寫的通道類型 <-chan int
/chan<- int
.
變數類型轉換方式
Go語言的轉換分兩種:類型轉換 和 類型斷言
1. 類型轉換
go裡面的類型轉換寫法:
T(x)
(1)、語法:<結果類型> := <目標類型> ( <表達式> )
(2)、類型轉換是用來在不同但相互相容的類型之間的相互轉換的方式,所以,當類型不相容的時候,是無法轉換的。如下:
var var1 int = 7 fmt.Printf("%T->%v\n", var1, var1) //int->7 var2 := float32(var1) fmt.Printf("%T->%v\n", var2, var2) //float32->7
值得註意的是,如果某些類型可能引起誤會,應該用括弧括起來轉換,如下:
//創建一個int變數,並獲得它的指針 var1 := new(int32) fmt.Printf("%T->%v\n", var1, var1) var2 := *int32(var1) fmt.Printf("%T->%v\n", var2, var2)
*int32(var1)相當於*(int32(var1)),一個指針,當然不能直接轉換成一個int32類型,所以該表達式直接編譯錯誤。將該表達式改為 (*int32)(var1)就可以正常輸出了。
2. 類型斷言(Type Assertion)
類型斷言用於提取介面的基礎值
go裡面的類型斷言寫法:
x.(T)
其中x為interface{}類型,T是要斷言的類型。
(1)語法:
<目標類型的值>,<布爾參數> := <表達式>.( 目標類型 ) // 安全類型斷言
<目標類型的值> := <表達式>.( 目標類型 ) //非安全類型斷言
(2)類型斷言的本質,跟類型轉換類似,都是類型之間進行轉換,不同之處在於,類型斷言實在介面之間進行,相當於Java中,對於一個對象,把一種介面的引用轉換成另一種。
x.(T) 檢查x的動態類型是否是T,其中x必須是介面值。
我們先來看一個最簡單的錯誤的類型斷言:
func test() { var i interface{} = "kk" j := i.(int) fmt.Printf("%T->%d\n", j, j) }
var i interface{} = "KK" 某種程度上相當於java中的,Object i = "KK";
現在把這個 i 轉換成 int 類型,系統內部檢測到這種不匹配,就會調用內置的panic()函數,拋出一個異常。
改一下,把 i 的定義改為:var i interface{} = 99,就沒問題了。輸出為:
int->99
以上是不安全的類型斷言。我們來看一下安全的類型斷言:
func test() { var i interface{} = "TT" j, b := i.(int) if b { fmt.Printf("%T->%d\n", j, j) } else { fmt.Println("類型不匹配") } }
輸出“類型不匹配”。
在理解有關介面的相關轉換前,我們先要理解Go語言中的介面類型:interface{}
定義格式:
type IA interface {} //聲明瞭一個介面(沒有函數集合時,是空介面類型)
申明一個空介面類型時,我們在定義“空介面類型”的變數時,可以賦值任意類型的值,如
package main import ( "fmt" ) type IA interface {} //空介面類型 func main() { var a IA = 1 //int var b IA = 1.1 //float var c IA = false //bool fmt.Println(a,b,c) }
介面類型(interface{})作為函數形式參數時,則該函數可以接受任意類型的變數,但對於函數內部,該變數仍然為interface{}類型(空介面類型)
package main import ( "fmt" ) func test(a interface{}) { fmt.Printf("%T->%v\n", a, a) } func main() { test(1) test(1.1) test(true) }
輸出結果:
int->1 float64->1.1 bool->true
什麼叫在在 函數內部,該變數任然為interface{}類型,如:
package main import "fmt" //用於輸出數組元素 func echoArray(a interface{}){ for _,v:=range a { fmt.Print(v," ") } fmt.Println() return } func main(){ a:=[]int{2,1,3,5,4} echoArray(a) }
以上代碼將會報錯,因為對於echoArray()而言,a是interface{}類型,而不是[]int類型
所以前面代碼中,將echoArray()做如下修改即可:
func echoArray(a interface{}){ b,_:=a.([]int)//通過斷言實現類型轉換 for _,v:=range b{ fmt.Print(v," ") } fmt.Println() return }
註意:在使用斷言時最好用 (安全類型斷言)
b,ok:=a.([]int) if ok{ ... }
或
b,ok:=a.([]int) if ok{ //斷言成功處理
... }
的形式,這樣能根據ok的值判斷斷言是否成功。
因為斷言失敗在編譯階段不會報錯,所以很可能出現斷言失敗導致運行錯誤,而你卻遲遲找不到原因(親身經歷)。
總結:“空介面類型”是動態類型的,即空介面類型的變數是任意類型的變數
通過上面的例子,我們對“空介面類型”有了瞭解,下麵我們就對“介面之間的轉換” 和 “介面和普通類型直接的轉換”作講解,來理解和介面之間的轉換到底是“類型轉換”還是“類型斷言”
介面之間轉換
Go語言中介面的類型轉換有很多奇怪的特性: 有時候是隱式轉換, 有時候需要類型斷言。
如:2個介面類型:
type IA interface {} //空介面類型 type IB interface {Foo()} //非空介面類型
1. 空介面類型轉非空介面類型
IA
要向IB
轉換如何操作呢?
這個操作無法在編譯期確定, 因此必然不是類型轉換.。由於2者都是介面類型, 因此肯定是類型斷言:
為什麼說“在編譯期確定”呢?通過上面的知識,我們知道 IA 是空介面類型,既然是空介面類型,說明IA對應的變數可以是任意類型,所以類型就無法確定(動態類型),所以在編譯的時候,都無法確定
var a A var b = a.(B)
當然因為上面的代碼中的a
是nil,
會導致a.(B)
錯誤。但是請註意: 這隻是運行錯誤, 並不是編譯錯誤!
結論:空介面類型轉非空介面類型時,是"類型斷言"轉換
2. 非空介面類型轉空介面類型
IB
要向IA
轉換如何操作呢? 因為IB是確定的類型,所以這個操作可以在編譯期確定(類型), 因此必然是類型轉換.
var b B var a = A(b)
前面我們說過, Go語言的介面是隱式轉換的, 因此還可以省略強制轉換的語句:
var b B var a = b
介面和類型之間的轉換(T(x))
雖然前面看到介面之間偶爾也會有類似普通類型之間的強制強制轉換語法,
但從本意上來說介面是一個特殊的類型(和普通的類型區別).
我們先定義2個和前面的IA
/IB
匹配的普通類型(底層類型一樣):
type IA interface {} //空介面類型 type IB interface {Foo()} //非空介面類型
type TA int type TB int func (TB) Foo(
) {}
如果是TA
和TB
之間的轉換, 可以參考前面的類型之間轉換的例子.
我們這裡重點關註 TA
/TB
和 IA
/IB
之間的轉換.
普通類型向介面類型轉換是隱式的(可以編譯期確定, 介面的隱式轉換特權):
var ta TA var ia = ta var tb TB var ib = tb
介面類型向普通類型是類型斷言(運行期確定):
var ia IA var ta = ia.(TA) var ib IB var tb = ib.(TB)
類型斷言在編譯期是沒有任何保障的, 錯誤的代碼也可以編譯通過:
var ta = ib.(TA) var tb = ia.(TB)
總結:
x.(T)
檢查x的動態類型是否是T,其中x必須是介面值。
- 如果T是具體類型,類型斷言檢查x的動態類型是否等於具體類型T。如果檢查成功,類型斷言返回的結果是x的動態值,其類型是T。換句話說,對介面值x斷言其動態類型是具體類型T,若成功則提取出x的具體值。如果檢查失敗則panic。
- 如果T是介面類型,類型斷言檢查x的動態類型是否滿足T。如果檢查成功,x的動態值不會被提取,返回值是一個類型為T的介面值。換句話說,到介面類型的類型斷言,改變了表達式的類型,改變了(通常是擴大了)可以訪問的方法,且保護了介面值內部的動態類型和值。
- 無論T是什麼類型,如果x是nil介面值,則類型斷言失敗。
- 類型斷言到一個較少限制(較少方法)的介面類型基本是不需要的,因為這個行為和賦值一樣(除了nil的情況)
- 如果我們想知道類型斷言是否失敗,而不是失敗時觸發panic,可以使用返回兩個值的版本:
y, ok := x.(T)