此篇文章將列出Go中所有的類型轉(zhuǎn)換、賦值和值比較規(guī)則。 請(qǐng)注意:在闡述這些規(guī)則的時(shí)候,自定義泛型中頻繁使用的類型參數(shù)類型被特意忽略掉了。 也就是說,本文不考慮涉及到自定義泛型的情形。
在Go中,如果一個(gè)值v
可以被顯式地轉(zhuǎn)換為類型T
,則此轉(zhuǎn)換可以使用語法形式(T)(v)
來表示。 在大多數(shù)情況下,特別是T
為一個(gè)類型名(即一個(gè)標(biāo)識(shí)符)時(shí),此形式可簡(jiǎn)化為T(v)
。
當(dāng)我們說一個(gè)值x
可以被隱式轉(zhuǎn)換為一個(gè)類型T
,這同時(shí)也意味著x
可以被顯式轉(zhuǎn)換為類型T
。
如果兩個(gè)類型表示著同一個(gè)類型,則它們的值可以相互隱式轉(zhuǎn)換為這兩個(gè)類型中的任意一個(gè)。比如,
byte
和uint8
的任何值可以轉(zhuǎn)換為這兩個(gè)類型中的任意一個(gè)。rune
和int32
的任何值可以轉(zhuǎn)換為這兩個(gè)類型中的任意一個(gè)。[]byte
和[]uint8
的任何值可以轉(zhuǎn)換為這兩個(gè)類型中的任意一個(gè)。此條規(guī)則沒什么可解釋的,無論你是否認(rèn)為此種情況中發(fā)生了轉(zhuǎn)換。
給定一個(gè)非接口值x
和一個(gè)非接口類型T
,并假設(shè)x
的類型為Tx
,
Tx
和T
的底層類型相同(忽略掉結(jié)構(gòu)體字段標(biāo)簽),則x
可以被顯式轉(zhuǎn)換為類型T
。Tx
和T
中至少有一個(gè)是無名類型并且它們的底層類型相同(考慮結(jié)構(gòu)體字段標(biāo)簽),則x
可以被隱式轉(zhuǎn)換為類型T
。Tx
和T
的底層類型不同,但是兩者都是無名的指針類型并且它們的基類型的底層類型相同(忽略掉結(jié)構(gòu)體字段標(biāo)簽),則x
可以(而且只能)被顯式轉(zhuǎn)換為類型T
。 (注意:兩處“忽略掉結(jié)構(gòu)體字段標(biāo)簽”從Go 1.8開始生效。)
一個(gè)例子:
package main
func main() {
// 類型[]int、IntSlice和MySlice共享底層類型:[]int。
type IntSlice []int
type MySlice []int
type Foo = struct{n int `foo`}
type Bar = struct{n int `bar`}
var s = []int{}
var is = IntSlice{}
var ms = MySlice{}
var x map[Bar]Foo
var y map[Foo]Bar
// 這兩行隱式轉(zhuǎn)換編譯不通過。
/*
is = ms
ms = is
*/
// 必須使用顯式轉(zhuǎn)換。
is = IntSlice(ms)
ms = MySlice(is)
x = map[Bar]Foo(y)
y = map[Foo]Bar(x)
// 這些隱式轉(zhuǎn)換是沒問題的。
s = is
is = s
s = ms
ms = s
}
指針相關(guān)的轉(zhuǎn)換例子:
package main
func main() {
type MyInt int
type IntPtr *int
type MyIntPtr *MyInt
var pi = new(int) // pi的類型為*int
var ip IntPtr = pi // 沒問題,因?yàn)榈讓宇愋拖嗤? // 并且pi的類型為無名類型。
// var _ *MyInt = pi // 不能隱式轉(zhuǎn)換
var _ = (*MyInt)(pi) // 顯式轉(zhuǎn)換是沒問題的
// 類型*int的值不能被直接轉(zhuǎn)換為類型MyIntPtr,
// 但是可以間接地轉(zhuǎn)換過去。
/*
var _ MyIntPtr = pi // 不能隱式轉(zhuǎn)換
var _ = MyIntPtr(pi) // 也不能顯式轉(zhuǎn)換
*/
var _ MyIntPtr = (*MyInt)(pi) // 間接隱式轉(zhuǎn)換沒問題
var _ = MyIntPtr((*MyInt)(pi)) // 間接顯式轉(zhuǎn)換沒問題
// 類型IntPtr的值不能被直接轉(zhuǎn)換為類型MyIntPtr,
// 但是可以間接地轉(zhuǎn)換過去。
/*
var _ MyIntPtr = ip // 不能隱式轉(zhuǎn)換
var _ = MyIntPtr(ip) // 也不能顯式轉(zhuǎn)換
*/
// 間接隱式或者顯式轉(zhuǎn)換都是沒問題的。
var _ MyIntPtr = (*MyInt)((*int)(ip)) // ok
var _ = MyIntPtr((*MyInt)((*int)(ip))) // ok
}
給定一個(gè)通道值x
,假設(shè)它的類型Tx
是一個(gè)雙向通道類型,T
也是一個(gè)通道類型(無論是雙向的還是單向的)。如果Tx
和T
的元素類型相同并且它們中至少有一個(gè)為無名類型,則x
可以被隱式轉(zhuǎn)換為類型T
。
一個(gè)例子:
package main
func main() {
type C chan string
type C1 chan<- string
type C2 <-chan string
var ca C
var cb chan string
cb = ca // ok,因?yàn)榈讓宇愋拖嗤? ca = cb // ok,因?yàn)榈讓宇愋拖嗤?
// 這4行都滿足此第3條轉(zhuǎn)換規(guī)則的條件。
var _, _ chan<- string = ca, cb // ok
var _, _ <-chan string = ca, cb // ok
var _ C1 = cb // ok
var _ C2 = cb // ok
// 類型C的值不能直接轉(zhuǎn)換為類型C1或C2。
/*
var _ = C1(ca) // compile error
var _ = C2(ca) // compile error
*/
// 但是類型C的值可以間接轉(zhuǎn)換為類型C1或C2。
var _ = C1((chan<- string)(ca)) // ok
var _ = C2((<-chan string)(ca)) // ok
var _ C1 = (chan<- string)(ca) // ok
var _ C2 = (<-chan string)(ca) // ok
}
給定一個(gè)值x
和一個(gè)接口類型I
,如果x
的類型(或者默認(rèn)類型)為Tx
并且類型Tx
實(shí)現(xiàn)了接口類型I
,則x
可以被隱式轉(zhuǎn)換為類型I
。 此轉(zhuǎn)換的結(jié)果為一個(gè)類型為I
的接口值。此接口值包裹了
x
的一個(gè)副本(如果Tx
是一個(gè)非接口值);x
的動(dòng)態(tài)值的一個(gè)副本(如果Tx
是一個(gè)接口值)。請(qǐng)閱讀接口一文獲取更多詳情和示例。
如果一個(gè)類型不確定值可以表示為類型T
的值,則它可以被隱式轉(zhuǎn)換為類型T
。
一個(gè)例子:
package main
func main() {
var _ []int = nil
var _ map[string]int = nil
var _ chan string = nil
var _ func()() = nil
var _ *bool = nil
var _ interface{} = nil
var _ int = 123.0
var _ float64 = 123
var _ int32 = 1.23e2
var _ int8 = 1 + 0i
}
(此規(guī)則和上一條規(guī)則有些重疊。)
常量的類型轉(zhuǎn)換結(jié)果一般仍然是一個(gè)常量。(除了下面第8條規(guī)則中將介紹的字符串轉(zhuǎn)換為字節(jié)切片或者碼點(diǎn)切片的情況。)
給定一個(gè)常量值x
和一個(gè)類型T
,如果x
可以表示成類型T
的一個(gè)值,則x
可以被顯式地轉(zhuǎn)換為類型T
;特別地,如果x
是一個(gè)類型不確定值,則它可以被隱式轉(zhuǎn)換為類型T
。
一個(gè)例子:
package main
func main() {
const I = 123
const I1, I2 int8 = 0x7F, -0x80
const I3, I4 int8 = I, 0.0
const F = 0.123456789
const F32 float32 = F
const F32b float32 = I
const F64 float64 = F
const F64b = float64(I3) // 這里必須顯式轉(zhuǎn)換
const C1, C2 complex64 = F, I
const I5 = int(C2) // 這里必須顯式轉(zhuǎn)換
}
非常量浮點(diǎn)數(shù)和整數(shù)值可以被顯式轉(zhuǎn)換為任何浮點(diǎn)數(shù)和整數(shù)類型。
非常量復(fù)數(shù)值可以被顯式轉(zhuǎn)換為任何復(fù)數(shù)類型。
注意,
一個(gè)例子:
package main
import "fmt"
func main() {
var a, b = 1.6, -1.6 // 類型均為float64
fmt.Println(int(a), int(b)) // 1 -1
var i, j int16 = 0x7FFF, -0x8000
fmt.Println(int8(i), uint16(j)) // -1 32768
var c1 complex64 = 1 + 2i
var _ = complex128(c1)
}
如果一個(gè)值的類型(或者默認(rèn)類型)為一個(gè)整數(shù)類型,則此值可以被當(dāng)作一個(gè)碼點(diǎn)值(rune值)顯式轉(zhuǎn)換為任何字符串類型。
一個(gè)字符串可以被顯式轉(zhuǎn)換為一個(gè)字節(jié)切片類型,反之亦然。 字節(jié)切片類型是指底層類型為[]byte
的類型。
一個(gè)字符串可以被顯式轉(zhuǎn)換為一個(gè)碼點(diǎn)切片類型,反之亦然。 碼點(diǎn)切片類型是指底層類型為[]rune
的類型。
請(qǐng)閱讀字符串一文獲取更多詳情和示例。
從Go 1.17開始,一個(gè)切片可以被轉(zhuǎn)化為一個(gè)相同元素類型的數(shù)組的指針類型。 但是如果數(shù)組的長度大于被轉(zhuǎn)化切片的長度,則將導(dǎo)致恐慌產(chǎn)生。
這里有一個(gè)例子。
從Go 1.20開始,一個(gè)切片可以被轉(zhuǎn)化為一個(gè)相同元素類型的數(shù)組。 但是如果數(shù)組的長度大于被轉(zhuǎn)化切片的長度,則將導(dǎo)致恐慌產(chǎn)生。
這里有一個(gè)例子。
非類型安全指針類型是指底層類型為unsafe.Pointer
的類型。
任何類型安全指針類型的值可以被顯式轉(zhuǎn)化為一個(gè)非類型安全指針類型,反之亦然。
任何uintptr值可以被顯式轉(zhuǎn)化為一個(gè)非類型安全指針類型,反之亦然。
請(qǐng)閱讀非類型安全指針一文獲取詳情和示例。
賦值可以看作是隱式類型轉(zhuǎn)換。 各種隱式轉(zhuǎn)換規(guī)則在上一節(jié)中已經(jīng)列出。
除了這些規(guī)則,賦值語句中的目標(biāo)值必須為一個(gè)可尋址的值、一個(gè)映射元素表達(dá)式或者一個(gè)空標(biāo)識(shí)符。
在一個(gè)賦值中,源值被復(fù)制給了目標(biāo)值。精確地說,源值的直接部分被復(fù)制給了目標(biāo)值。
注意:函數(shù)傳參和結(jié)果返回其實(shí)都是賦值。
Go白皮書提到:
在任何比較中,第一個(gè)比較值必須能被賦值給第二個(gè)比較值的類型,或者反之。
所以,值比較規(guī)則和賦值規(guī)則非常相似。 換句話說,兩個(gè)值是否可以比較取決于其中一個(gè)值是否可以隱式轉(zhuǎn)換為另一個(gè)值的類型。 很簡(jiǎn)單?此規(guī)則描述基本正確,但是存在另外一條優(yōu)先級(jí)更高的規(guī)則:
如果一個(gè)比較表達(dá)式中的兩個(gè)比較值均為類型確定值,則它們的類型必須都屬于可比較類型。
按照上面這條規(guī)則,如果一個(gè)不可比較類型(肯定是一個(gè)非接口類型)實(shí)現(xiàn)了一個(gè)接口類型,則比較這兩個(gè)類型的值是非法的,即使前者的值可以隱式轉(zhuǎn)化為后者。
注意,盡管切片/映射/函數(shù)類型為不可比較類型,但是它們的值可以和類型不確定的預(yù)聲明nil
標(biāo)識(shí)符比較。
上述規(guī)則并未覆蓋所有的情況。如果兩個(gè)值均為類型不確定值,它們可以比較嗎?這種情況的規(guī)則比較簡(jiǎn)單:
兩個(gè)類型不確定的數(shù)字值的比較結(jié)果服從直覺。
注意,兩個(gè)類型不確定的nil值不能相互比較。
任何比較的結(jié)果均為一個(gè)類型不確定的布爾值。
一些值比較的例子:
package main
// 一些類型為不可比較類型的變量。
var s []int
var m map[int]int
var f func()()
var t struct {x []int}
var a [5]map[int]int
func main() {
// 這些比較編譯不通過。
/*
_ = s == s
_ = m == m
_ = f == f
_ = t == t
_ = a == a
_ = nil == nil
_ = s == interface{}(nil)
_ = m == interface{}(nil)
_ = f == interface{}(nil)
*/
// 這些比較編譯都沒問題。
_ = s == nil
_ = m == nil
_ = f == nil
_ = 123 == interface{}(nil)
_ = true == interface{}(nil)
_ = "abc" == interface{}(nil)
}
假設(shè)兩個(gè)值可以相互比較,并且它們的類型同為T
。 (如果它們的類型不同,則其中一個(gè)可以轉(zhuǎn)換為另一個(gè)的類型。這里我們不考慮兩者均為類型不確定值的情形。)
T
是一個(gè)布爾類型,則這兩個(gè)值只有在它們同為true
或者false
的時(shí)候比較結(jié)果才為true
。
T
是一個(gè)整數(shù)類型,則這兩個(gè)值只有在它們?cè)趦?nèi)存中的表示完全一致的情況下比較結(jié)果才為true
。
T
是一個(gè)浮點(diǎn)數(shù)類型, 則這兩個(gè)值只要滿足下面任何一種情況,它們的比較結(jié)果就為true
:
+Inf
;
-Inf
;
-0.0
或者都為+0.0
。
NaN
并且它們?cè)趦?nèi)存中的表示完全一致。
T
是一個(gè)復(fù)數(shù)類型,則這兩個(gè)值只有在它們的實(shí)部和虛部均做為浮點(diǎn)數(shù)進(jìn)行進(jìn)行比較的結(jié)果都為true
的情況下比較結(jié)果才為true
。
T
是一個(gè)指針類型(類型安全或者非類型安全),則這兩個(gè)值只有在它們所表示的地址值相等或者它們都為nil的情況下比較結(jié)果才為true
。
T
是一個(gè)通道類型,則這兩個(gè)值只有在它們引用著相同的底層內(nèi)部通道或者它們都為nil時(shí)比較結(jié)果才為true
。
T
是一個(gè)結(jié)構(gòu)體類型,則它們的相應(yīng)字段將逐對(duì)進(jìn)行比較。只要有一對(duì)字段不相等,這兩個(gè)結(jié)構(gòu)體值就不相等。
T
是一個(gè)數(shù)組類型,則它們的相應(yīng)元素將逐對(duì)進(jìn)行比較。只要有一對(duì)元素不相等,這兩個(gè)結(jié)構(gòu)體值就不相等。
T
是一個(gè)接口類型,請(qǐng)參閱兩個(gè)接口值是如何進(jìn)行比較的。
T
是一個(gè)字符串類型,請(qǐng)參閱兩個(gè)字符串值是如何進(jìn)行比較的。
請(qǐng)注意,動(dòng)態(tài)類型均為同一個(gè)不可比較類型的兩個(gè)接口值的比較將產(chǎn)生一個(gè)恐慌。比如下面的例子:
package main
func main() {
type T struct {
a interface{}
b int
}
var x interface{} = []int{}
var y = T{a: x}
var z = [3]T{{a: y}}
// 這三個(gè)比較均會(huì)產(chǎn)生一個(gè)恐慌。
_ = x == x
_ = y == y
_ = z == z
}
更多建議: