概述
#在最近的面試中被面試官問到go之間的類型比較,回答的並不是非常好,根本上來說還是基礎不夠牢固!看了網路上的一堆資料,自己做了一些簡單的總結,哈哈!
go中的型別
首先來看看go包含的最基礎的集中型別
- 基本型別:go中最基本型別包含整數型(
int、uint、int8、uint8、int16、uint16、int32、uint32、int64、uint64、byte、rune
等)、浮點型(float32、float64
)、字串(string
也是個[]rune陣列)和比較不常用的複數型別(complex64/complex128
)。 - 複合型別:主要包括
結構體
和陣列
。 - 引用類型:
Slice、Map、Channel、指標
。 - 介面類型:
Error、io.Reader等
。
go作為強型別語言並不會和PHP等高階語言自動幫我們進行型別轉換,所以我們在比較時必須用==
兩邊的型別必須一致,即使他們底部類型一致也不行。看下面的程式碼
package main import "fmt" type A struct { Id int } type B struct { Id int } func main() { var a int var b int16 // 编译报错:invalid operation a == b (mismatched types int and int16) fmt.Println(a == b) aStruct := A{Id:5} bStruct := B{Id:5} // 编译报错:invalid operation: aStruct == bStruct (mismatched types A and B) fmt.Println(aStruct == bStruct) }
所以go不會幫我們做隱含轉換,即使底層的型別一致,也不能比較。
接下來我們從不同的類型來分析是否可以進行比較。
基本型別
go的基本型別就比較簡單,只要型別是一樣的,那麼他們就是可以比較的,舉個栗子:
package main import "fmt" func main() { var a int = 0 var b int = 1 // 输出false fmt.Println(a == b) }
不過基本型別中也要注意浮點型的比較就不像我們現實中的一樣,例如0.1 0.2在計算中運行結果就不是0.3了,而是0.30000000000000004了
package main import "fmt" func main() { var a float64=0.1 var b float64=0.2 // 0.30000000000000004 fmt.Println(a+b) }
為什麼會這樣,可以看下draveness( https://github.com/draveness) 大佬的這篇文章https://draveness.me/whys-the...
複合型別
陣列
面試中也常會問到go數組和切片的差別。數組在go中是必須先確定長度的,也就是長度不能再去擴容。而且它是個值拷貝,做參數傳到一個函數中被修改,那麼外部的值還是一樣的不變的。 Slice則相反。那麼數組是否可以比較呢,看下面的例子:
package main import "fmt" func main() { a := [2]int{1, 2} b := [2]int{1, 2} c := [2]int{1, 3} d := [3]int{1, 2, 4} fmt.Println(a == b) // true fmt.Println(a == c) // false fmt.Println(a == d) // invalid operation: a == d (mismatched types [2]int and [3]int) }
可以看出,相同長度的數組是可以比較的,而不同長度的數組是不能進行比較的
。原因是什麼呢?這是因為數組類型中,數組的長度也是類型的一部分,不同長度的數組那麼他們的類型也就被認為不同的,所以無法比較。
結構體
同樣的Struct
也是一樣的。 Struct
的比較也從內部型別開始比較,每一個類型的值相等才是相等的。如下例:
package main import "fmt" type A struct { id int name string } func main() { a := A{id:5,name:"123"} b := A{id:5,name:"123"} c := A{id:5,name:"1234"} fmt.Println(a == b) // true fmt.Println(a == c) // false }
那麼可以理解成Struct
結構體是可以比較的嗎。我們再來看個例子:
package main import "fmt" type A struct { id int name string son []int } func main() { a := A{id:5,name:"123",son:[]int{1,2,3}} b := A{id:5,name:"123",son:[]int{1,2,3}} fmt.Println(a == b) // invalid operation: a == b (struct containing []int cannot be compared) }
怎麼又變成不可比較的呢?這就要看下面的引用類型了。
引用類型
上面中的範例結構體中帶上切片就無法比較了,在go中Slice
和Map
#被定義成不能比較的類型。我們來看
如果Slice
是可比較,那麼用什麼來定義是一樣的切片呢?如果用位址,那麼如果兩個位址指向的Slice
是一樣的呢?這顯然不合適。如果跟數組一樣的方式,那麼我切片擴容了呢,就不相等了。所以長度和容量導致不好比較。雖然可以在語言層面解決這個問題,但是 golang 團隊認為不值得為此耗費精力。所以Slice
被當成不可比較。
同樣的Map
也被定義成不可比較型別。那麼引用型別都是不可比較嗎?也不是,看個例子:
package main import "fmt" type A struct { id int name string } func main() { a := &A { a : 1, b : "test1" } b := &A { a : 1, b : "test1" } c := a fmt.Println(a == b) // false fmt.Println(a == c) // true ch1 := make(chan int, 1) ch2 := make(chan int, 1) ch3 := ch1 fmt.Println(ch1 == ch2) // false fmt.Println(ch1 == ch3) // true }
引用型別變數儲存的是某個變數的記憶體位址。所以引用型別變數的比較,判斷的是這兩個引用型別儲存的是不是同一個變數。
- 如果是同一個變量,則記憶體位址肯定也一樣,則引用型別變數相等,用"=="判斷為true
- #如果不是同一個變量,則記憶體位址肯定不一樣,"=="結果為false
介面類型
#Go 語言根據介面類型是否包含一組方法將介面類型分成了兩類:
- 使用
runtime.iface
结构体表示包含方法的接口 - 使用
runtime.eface
结构体表示不包含任何方法的interface{}
类型
type eface struct { // 16 字节 _type *_type data unsafe.Pointer } type iface struct { // 16 字节 tab *itab data unsafe.Pointer }
所以我们可以得知,一个接口值是由两个部分组成的,即该接口对应的类型和接口对应具体的值。接口值的比较涉及这两部分的比较,只有当类型和值都相等(动态值使用==
比较),两个接口值才是相等的。看个例子:
var a interface{} = 0 var b interface{} = 2 var c interface{} = 0 var d interface{} = 0.0 fmt.Println(a == b) // false fmt.Println(a == c) // true fmt.Println(a == d) // false
a
和c
类型相同(都是int
),值也相同(都是0
,基本类型比较),故两者相等。 a
和b
类型相同,值不等,故两者不等。 a
和d
类型不同,a
为int
,d
为float64
,故两者不等。
type A struct { a int b string } var a interface{} = A { a: 1, b: "test" } var b interface{} = A { a: 1, b: "test" } var c interface{} = A { a: 2, b: "test" } fmt.Println(a == b) // true fmt.Println(a == c) // false var d interface{} = &A { a: 1, b: "test" } var e interface{} = &A { a: 1, b: "test" } fmt.Println(d == e) // false
a
和b
类型相同(都是A
),值也相同(结构体A
),故两者相等。 a
和c
类型相同,值不同,故两者不等。 d
和e
类型相同(都是*A
),值使用指针(引用)类型的比较,由于不是指向同一个地址,故不等。
不过需要注意的是,如果接口中类型是切片或者Map
不可比较的类型,那么会直接报错的。看个例子:
var a interface{} = []int{1, 2} var b interface{} = []int{1, 2} // panic: runtime error: comparing uncomparable type []int fmt.Println(a == b)
a
和b
的类型是切片类型,而切片类型不可比较,所以a == b
会panic
。
接口值的比较不要求接口类型(注意不是动态类型)完全相同,只要一个接口可以转化为另一个就可以比较。例如:
var f *os.File var r io.Reader = f var rc io.ReadCloser = f fmt.Println(r == rc) // true var w io.Writer = f // invalid operation: r == w (mismatched types io.Reader and io.Writer) fmt.Println(r == w)
r
的类型为io.Reader
接口,rc
的类型为io.ReadCloser
接口。查看源码,io.ReadCloser
的定义如下:
type ReadCloser interface { Reader Closer }
io.ReadCloser
可转化为io.Reader
,故两者可比较。
而io.Writer
不可转化为io.Reader
,编译报错。
总结
- 可比较:
int、ifloat、string、bool、complex、pointe、channel、interface、array
- 不可比较:
slice、map、function
- 复合类型中如果带有不可比较的类型,那么该类型也是不可比较的。可以理解不可比较类型具有传递性。