深入理解Go之==的使用
概述
相信==判等操作,大家每天都在用。之前在論壇上看到不少人在問 golang ==比較的結果??吹胶芏嗳藢?golang 中==的結果不太了解。確實,golang 中對==的處理有一些細節(jié)的地方需要特別注意。雖然平時可能不太會遇到,但是碰到了就是大坑。本文將對 golang 中==操作做一個系統(tǒng)的介紹。希望能對大家有所幫助。
類型
golang 中的數據類型可以分為以下 4 大類:
- 基本類型:整型(
int/uint/int8/uint8/int16/uint16/int32/uint32/int64/uint64/byte/rune等)、浮點數(float32/float64)、復數類型(complex64/complex128)、字符串(string)。 - 復合類型(又叫聚合類型):數組和結構體類型。
- 引用類型:切片(slice)、map、channel、指針。
- 接口類型:如
error。
==操作最重要的一個前提是:兩個操作數類型必須相同!類型必須相同!類型必須相同!
如果類型不同,那么編譯時就會報錯。
注意:
- golang 的類型系統(tǒng)非常嚴格,沒有
C/C++中的隱式類型轉換。雖然寫起來稍微有些麻煩,但是能避免今后非常多的麻煩?。?! - golang 中可以通過
type定義新類型。新定義的類型與底層類型不同,不能直接比較。
為了更容易看出類型,示例代碼中的變量定義都顯式指定了類型。
看下面的代碼:
package main
import "fmt"
func main() {
var a int8
var b int16
// 編譯錯誤:invalid operation a == b (mismatched types int8 and int16)
fmt.Println(a == b)
}沒有隱式類型轉換。
package main
import "fmt"
func main() {
type int8 myint8
var a int8
var b myint8
// 編譯錯誤:invalid operation a == b (mismatched types int8 and myint8)
fmt.Println(a == b)
}雖然myint8的底層類型是int8,但是他們是不同的類型。
下面依次通過這 4 種類型來說明==是如何做比較的。
基本類型
這是最簡單的一種類型。比較操作也很簡單,直接比較值是否相等。沒啥好說的,直接看例子。
var a uint32 = 10 var b uint32 = 20 var c uint32 = 10 fmt.Println(a == b) // false fmt.Println(a == c) // true
有一點需要注意,浮點數的比較問題:
var a float64 = 0.1 var b float64 = 0.2 var c float64 = 0.3 fmt.Println(a + b == c) // false
因為計算機中,有些浮點數不能精確表示,浮點運算結果會有誤差。如果我們分別輸出a+b和c的值,會發(fā)現(xiàn)它們確實是不同的:
fmt.Println(a + b) fmt.Println(c) // 0.30000000000000004 // 0.3
這個問題不是 golang 獨有的,只要浮點數遵循 IEEE 754 標準的編程語言都有這個問題。需要特別注意,盡量不要做浮點數比較,確實需要比較時,計算兩個浮點數的差的絕對值,如果小于一定的值就認為它們相等,比如1e-9。
復合類型
復合類型也叫做聚合類型。golang 中的復合類型只有兩種:數組和結構體。它們是逐元素/字段比較的。
注意:數組的長度視為類型的一部分,長度不同的兩個數組是不同的類型,不能直接比較。
- 對于數組來說,依次比較各個元素的值。根據元素類型的不同,再依據是基本類型、復合類型、引用類型或接口類型,按照特定類型的規(guī)則進行比較。所有元素全都相等,數組才是相等的。
- 對于結構體來說,依次比較各個字段的值。根據字段類型的不同,再依據是 4 中類型中的哪一種,按照特定類型的規(guī)則進行比較。所有字段全都相等,結構體才是相等的。
例如:
a := [4]int{1, 2, 3, 4}
b := [4]int{1, 2, 3, 4}
c := [4]int{1, 3, 4, 5}
fmt.Println(a == b) // true
fmt.Println(a == c) // false
type A struct {
a int
b string
}
aa := A { a : 1, b : "test1" }
bb := A { a : 1, b : "test1" }
cc := A { a : 1, b : "test2" }
fmt.Println(aa == bb)
fmt.Println(aa == cc)引用類型
引用類型是間接指向它所引用的數據的,保存的是數據的地址。引用類型的比較實際判斷的是兩個變量是不是指向同一份數據,它不會去比較實際指向的數據。
例如:
type A struct {
a int
b string
}
aa := &A { a : 1, b : "test1" }
bb := &A { a : 1, b : "test1" }
cc := aa
fmt.Println(aa == bb)
fmt.Println(aa == cc)因為aa和bb指向的兩個不同的結構體,雖然它們指向的值是相等的(見上面復合類型的比較),但是它們不等。 aa和cc指向相同的結構體,所以它們相等。
再看看channel的比較:
ch1 := make(chan int, 1) ch2 := make(chan int, 1) ch3 := ch1 fmt.Println(ch1 == ch2) fmt.Println(ch1 == ch3)
ch1和ch2雖然類型相同,但是指向不同的channel,所以它們不等。 ch1和ch3指向相同的channel,所以它們相等。
關于引用類型,有兩個比較特殊的規(guī)定:
- 切片之間不允許比較。切片只能與
nil值比較。 map之間不允許比較。map只能與nil值比較。
為什么要做這樣的規(guī)定?我們先來說切片。因為切片是引用類型,它可以間接的指向自己。例如:
a := []interface{}{ 1, 2.0 }
a[1] = a
fmt.Println(a)
// !!!
// runtime: goroutine stack exceeds 1000000000-byte limit
// fatal error: stack overflow上面代碼將a賦值給a[1]導致遞歸引用,fmt.Println(a)語句直接爆棧。
- 切片如果直接比較引用地址,是不合適的。首先,切片與數組是比較相近的類型,比較方式的差異會造成使用者的混淆。另外,長度和容量是切片類型的一部分,不同長度和容量的切片如何比較?
- 切片如果像數組那樣比較里面的元素,又會出現(xiàn)上來提到的循環(huán)引用的問題。雖然可以在語言層面解決這個問題,但是 golang 團隊認為不值得為此耗費精力。
基于上面兩點原因,golang 直接規(guī)定切片類型不可比較。使用==比較切片直接編譯報錯。
例如:
var a []int var b []int // invalid operation: a == b (slice can only be compared to nil) fmt.Println(a == b)
錯誤信息很明確。
因為map的值類型可能為不可比較類型(見下面,切片是不可比較類型),所以map類型也不可比較??。
接口類型
接口類型是 golang 中比較重要的一種類型。接口類型的值,我們稱為接口值。一個接口值是由兩個部分組成的,具體類型(即該接口存儲的值的類型)和該類型的一個值。引用《go 程序設計語言》的名稱,分別稱為動態(tài)類型和動態(tài)值。接口值的比較涉及這兩部分的比較,只有當動態(tài)類型完全相同且動態(tài)值相等(動態(tài)值使用==比較),兩個接口值才是相等的。
例如:
var a interface{} = 1
var b interface{} = 1
var c interface{} = 2
var d interface{} = 1.0
fmt.Println(a == b) // false
fmt.Println(a == c) // true
fmt.Println(a == d) // falsea和b動態(tài)類型相同(都是int),動態(tài)值也相同(都是1,基本類型比較),故兩者相等。 a和c動態(tài)類型相同,動態(tài)值不等(分別為1和2,基本類型比較),故兩者不等。 a和d動態(tài)類型不同,a為int,d為float64,故兩者不等。
type A struct {
a int
b string
}
var aa interface{} = A { a: 1, b: "test" }
var bb interface{} = A { a: 1, b: "test" }
var cc interface{} = A { a: 2, b: "test" }
fmt.Println(aa == bb) // true
fmt.Println(aa == cc) // false
var dd interface{} = &A { a: 1, b: "test" }
var ee interface{} = &A { a: 1, b: "test" }
fmt.Println(dd == ee) // false
復制代碼aa和bb動態(tài)類型相同(都是A),動態(tài)值也相同(結構體A,見上面復合類型的比較規(guī)則),故兩者相等。 aa和cc動態(tài)類型相同,動態(tài)值不同,故兩者不等。 dd和ee動態(tài)類型相同(都是*A),動態(tài)值使用指針(引用)類型的比較,由于不是指向同一個地址,故不等。
注意:
如果接口的動態(tài)值不可比較,強行比較會panic?。?!
var a interface{} = []int{1, 2, 3, 4}
var b interface{} = []int{1, 2, 3, 4}
// panic: runtime error: comparing uncomparable type []int
fmt.Println(a == b)a和b的動態(tài)值是切片類型,而切片類型不可比較,所以a == b會panic。
接口值的比較不要求接口類型(注意不是動態(tài)類型)完全相同,只要一個接口可以轉化為另一個就可以比較。例如:
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,編譯報錯。
使用type定義的類型
使用type可以基于現(xiàn)有類型定義新的類型。新類型會根據它們的底層類型來比較。例如:
type myint int
var a myint = 10
var b myint = 20
var c myint = 10
fmt.Println(a == b) // false
fmt.Println(a == c) // true
type arr4 [4]int
var aa arr4 = [4]int{1, 2, 3, 4}
var bb arr4 = [4]int{1, 2, 3, 4}
var cc arr4 = [4]int{1, 2, 3, 5}
fmt.Println(aa == bb)
fmt.Println(aa == cc)myint根據底層類型int來比較。 arr4根據底層類型[4]int來比較。
不可比較性
前面說過,golang 中的切片類型是不可比較的。所有含有切片的類型都是不可比較的。例如:
- 數組元素是切片類型。
- 結構體有切片類型的字段。
- 指針指向的是切片類型。
不可比較性會傳遞,如果一個結構體由于含有切片字段不可比較,那么將它作為元素的數組不可比較,將它作為字段類型的結構體不可比較。
談談map
由于map的key是使用==來判等的,所以所有不可比較的類型都不能作為map的key。例如:
// invalid map key type []int
m1 := make(map[[]int]int)
type A struct {
a []int
b string
}
// invalid map key type A
m2 := make(map[A]int)由于切片類型不可比較,不能作為map的key,編譯時m1 := make(map[[]int]int)報錯。 由于結構體A含有切片字段,不可比較,不能作為map的key,編譯報錯。
總結
到此這篇關于深入理解Go之==的使用的文章就介紹到這了,更多相關Go ==內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
go?module化?import?調用本地模塊?tidy的方法
這篇文章主要介紹了go?module化?import?調用本地模塊?tidy的相關知識,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-09-09
Golang并發(fā)編程之main goroutine的創(chuàng)建與調度詳解
這篇文章主要為大家詳細介紹了Golang并發(fā)編程中main goroutine的創(chuàng)建與調度,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2023-03-03
詳解GO語言中[]byte與string的兩種轉換方式和底層實現(xiàn)
這篇文章主要為大家詳細介紹了GO語言中[]byte與string的兩種轉換方式和底層實現(xiàn)的相關知識,文中的示例代碼講解詳細,有需要的小伙伴可以參考下2024-03-03

