Go語言 類型轉(zhuǎn)換、賦值和值比較規(guī)則大全

2023-02-16 17:40 更新

此篇文章將列出Go中所有的類型轉(zhuǎn)換、賦值和值比較規(guī)則。 請(qǐng)注意:在闡述這些規(guī)則的時(shí)候,自定義泛型中頻繁使用的類型參數(shù)類型被特意忽略掉了。 也就是說,本文不考慮涉及到自定義泛型的情形。

類型轉(zhuǎn)換規(guī)則大全

在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

1. 顯然的類型轉(zhuǎn)換規(guī)則

如果兩個(gè)類型表示著同一個(gè)類型,則它們的值可以相互隱式轉(zhuǎn)換為這兩個(gè)類型中的任意一個(gè)。比如,

  • 類型byteuint8的任何值可以轉(zhuǎn)換為這兩個(gè)類型中的任意一個(gè)。
  • 類型runeint32的任何值可以轉(zhuǎn)換為這兩個(gè)類型中的任意一個(gè)。
  • 類型[]byte[]uint8的任何值可以轉(zhuǎn)換為這兩個(gè)類型中的任意一個(gè)。

此條規(guī)則沒什么可解釋的,無論你是否認(rèn)為此種情況中發(fā)生了轉(zhuǎn)換。

2. 底層類型相關(guān)的類型轉(zhuǎn)換規(guī)則

給定一個(gè)非接口值x和一個(gè)非接口類型T,并假設(shè)x的類型為Tx,

  • 如果類型TxT底層類型相同(忽略掉結(jié)構(gòu)體字段標(biāo)簽),則x可以被顯式轉(zhuǎn)換為類型T。
  • 如果類型TxT中至少有一個(gè)是無名類型并且它們的底層類型相同(考慮結(jié)構(gòu)體字段標(biāo)簽),則x可以被隱式轉(zhuǎn)換為類型T。
  • 如果類型TxT的底層類型不同,但是兩者都是無名的指針類型并且它們的基類型的底層類型相同(忽略掉結(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
}

3. 通道相關(guān)的類型轉(zhuǎn)換規(guī)則

給定一個(gè)通道值x,假設(shè)它的類型Tx是一個(gè)雙向通道類型,T也是一個(gè)通道類型(無論是雙向的還是單向的)。如果TxT的元素類型相同并且它們中至少有一個(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
}

4. 和接口實(shí)現(xiàn)相關(guān)的類型轉(zhuǎn)換規(guī)則

給定一個(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)閱讀接口一文獲取更多詳情和示例。

5. 類型不確定值相關(guān)的類型轉(zhuǎn)換規(guī)則

如果一個(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
}

6. 常量相關(guān)的類型轉(zhuǎn)換規(guī)則

(此規(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)換
}

7. 非常量數(shù)值轉(zhuǎn)換規(guī)則

非常量浮點(diǎn)數(shù)和整數(shù)值可以被顯式轉(zhuǎn)換為任何浮點(diǎn)數(shù)和整數(shù)類型。

非常量復(fù)數(shù)值可以被顯式轉(zhuǎn)換為任何復(fù)數(shù)類型。

注意,

  • 非常量復(fù)數(shù)值不能被轉(zhuǎn)換為浮點(diǎn)數(shù)或整數(shù)類型。
  • 非常量浮點(diǎn)數(shù)和整數(shù)值不能被轉(zhuǎn)換為復(fù)數(shù)類型。
  • 在非常量數(shù)值的轉(zhuǎn)換過程中,溢出和舍入是允許的。當(dāng)一個(gè)浮點(diǎn)數(shù)被轉(zhuǎn)換為整數(shù)時(shí),小數(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)
}

8. 字符串相關(guān)的轉(zhuǎn)換規(guī)則

如果一個(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)閱讀字符串一文獲取更多詳情和示例。

9. 切片相關(guān)的類型轉(zhuǎn)換規(guī)則

從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è)例子

10. 非類型安全指針相關(guān)的類型轉(zhuǎn)換規(guī)則

非類型安全指針類型是指底層類型為unsafe.Pointer的類型。

任何類型安全指針類型的值可以被顯式轉(zhuǎn)化為一個(gè)非類型安全指針類型,反之亦然。

任何uintptr值可以被顯式轉(zhuǎn)化為一個(gè)非類型安全指針類型,反之亦然。

請(qǐng)閱讀非類型安全指針一文獲取詳情和示例。

賦值規(guī)則

賦值可以看作是隱式類型轉(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í)都是賦值。

值比較規(guī)則

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è)類型不確定的布爾值可以相互比較。
  • 兩個(gè)類型不確定的數(shù)字值可以相互比較。
  • 兩個(gè)類型不確定的字符串值可以相互比較。

兩個(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)
}

兩個(gè)值是如何進(jìn)行比較的?

假設(shè)兩個(gè)值可以相互比較,并且它們的類型同為T。 (如果它們的類型不同,則其中一個(gè)可以轉(zhuǎn)換為另一個(gè)的類型。這里我們不考慮兩者均為類型不確定值的情形。)

  1. 如果T是一個(gè)布爾類型,則這兩個(gè)值只有在它們同為true或者false的時(shí)候比較結(jié)果才為true。
  2. 如果T是一個(gè)整數(shù)類型,則這兩個(gè)值只有在它們?cè)趦?nèi)存中的表示完全一致的情況下比較結(jié)果才為true。
  3. 如果T是一個(gè)浮點(diǎn)數(shù)類型, 則這兩個(gè)值只要滿足下面任何一種情況,它們的比較結(jié)果就為true
    • 它們都為+Inf;
    • 它們都為-Inf;
    • 它們都為-0.0或者都為+0.0。
    • 它們都不是NaN并且它們?cè)趦?nèi)存中的表示完全一致。
  4. 如果T是一個(gè)復(fù)數(shù)類型,則這兩個(gè)值只有在它們的實(shí)部和虛部均做為浮點(diǎn)數(shù)進(jìn)行進(jìn)行比較的結(jié)果都為true的情況下比較結(jié)果才為true。
  5. 如果T是一個(gè)指針類型(類型安全或者非類型安全),則這兩個(gè)值只有在它們所表示的地址值相等或者它們都為nil的情況下比較結(jié)果才為true。
  6. 如果T是一個(gè)通道類型,則這兩個(gè)值只有在它們引用著相同的底層內(nèi)部通道或者它們都為nil時(shí)比較結(jié)果才為true。
  7. 如果T是一個(gè)結(jié)構(gòu)體類型,則它們的相應(yīng)字段將逐對(duì)進(jìn)行比較。只要有一對(duì)字段不相等,這兩個(gè)結(jié)構(gòu)體值就不相等。
  8. 如果T是一個(gè)數(shù)組類型,則它們的相應(yīng)元素將逐對(duì)進(jìn)行比較。只要有一對(duì)元素不相等,這兩個(gè)結(jié)構(gòu)體值就不相等。
  9. 如果T是一個(gè)接口類型,請(qǐng)參閱兩個(gè)接口值是如何進(jìn)行比較的。
  10. 如果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
}


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)