這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
類型轉換和類型斷言是Go語言中比較繞的地方.
Go語言要求所有統一運算式的不同的類型之間必須做顯示的類型轉換.
而作為Go語言鼻祖的C語言是可以直接做隱式的類型轉換的.
當然作為const類的字面值常量是要靈活很多的.
不過Go語言必須做顯示的類型轉換的要求也有例外的情況:
- 當普通
T類型變數向I介面類型轉換時是隱式的!
- 當
IX介面變數向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 intfunc()(x) // function signature func() x(func())(x) // x is converted to func()(func() int)(x) // x is converted to func() intfunc() int(x) // x is converted to func() int (unambiguous)
簡單的說, x需要轉換為T類型的文法是T(x).
如果對於某些地方的優先順序拿不準可以自己加()約束.
最後一個轉換就是一個容易混淆的語句, 因此需要用括弧(func() int)(x)提高優先順序.
還有一個容易混淆的地方是 唯讀和唯寫的通道類型 <-chan int/chan<- int .
介面之間轉換的例子
Go語言中介面的類型轉換有很多奇怪的特性: 有時候是隱式轉換, 有時候需要類型斷言.
Go語言的介面之間雖然也有強制轉換的文法, 但是因為介面間支援隱式轉換, 因此介面之間
的強制轉換文法只是一個擺設.
比如有以下2個介面類型:
type IA interface {}type IB interface {Foo()}
IA要向IB轉換如何操作呢? 這個操作無法在編譯期確定, 因此必然不是類型轉換.
由於2者都是介面類型, 因此肯定是類型斷言:
var a Avar b = a.(B)
當然因為上面的代碼中的a是nil, 會導致a.(B)錯誤.
但是請注意: 這隻是運行錯誤, 並不是編譯錯誤!
IB要向IA轉換如何操作呢? 這個操作可以在編譯期確定, 因此必然是類型轉換.
var b Bvar a = A(b)
前面我們說過, Go語言的介面是隱式轉換的, 因此還可以省略強制轉換的語句:
var b Bvar a = b
介面和類型之間的轉換例子
雖然前面看到介面之間偶爾也會有類似普通類型之間的強制強制轉換文法,
但從本意上來說介面是一個特殊的類型(和普通的類型區別).
我們先定義2個和前面的IA/IB匹配的普通類型(底層類型一樣):
type TA inttype TB intfunc (TB) Foo() {}
如果是TA和TB之間的轉換, 可以參考前面的類型之間轉換的例子.
我們這裡重點關注 TA/TB 和 IA/IB 之間的轉換.
普通類型向介面類型轉換是隱式的(可以編譯期確定, 介面的隱式轉換特權):
var ta TAvar ia = tavar tb TBvar ib = tb
介面類型向普通類型是類型斷言(運行期確定):
var ia IAvar ta = ia.(TA)var ib IBvar tb = ib.(TB)
類型斷言在編譯期是沒有任何保障的, 錯誤的代碼也可以編譯通過:
var ta = ib.(TA)var tb = ia.(TB)
總結
因為Go語言的類型轉換和類型斷言設計的不夠完美, 因此很難簡單歸納.
下面是我整理的判斷是類型轉換還是類型斷言的虛擬碼:
func x 轉換為 y if x 是介面嗎 ? { if 可以編譯期轉換嗎 ? { 這是類型轉換 (介面之間可以隱式轉換) // 這裡也可以用類型斷言, 如果編譯期不最佳化的效率可能低一些 } else { 這是類型斷言 (運行時也可能會失敗) } } else { if 可以編譯期轉換 ? { 這是類型轉換 (顯示轉換, 必須成功, 但可能會遺失資料) } else { 禁止! } }}
我們可以看到, 當介面之間可以用類型轉換的時候, 其實也是可以用類型斷言的.
Go語言介面之間的轉換是最混亂的特徵之一. 期望Go2.0能夠有所改善.