背景交代
大家經常用"=="來比較兩個變量是否相等。但是golang中的"=="有很多細節的地方,跟php是不一樣的。很多時候不能直接用"=="來比較,編譯器會直接報錯。
golang中基本類型的比較規則和復合類型的不一致,先介紹下golang的變量類型:
- 1,基本類型
- 整型,包括int,uint,int8,uint8,int16,uint16,int32,uint32,int64,uint64,byte,rune,uintptr等
- 浮點型,包括float32,float64
- 復數類型,包括complex64,complex128
- 字符串類型,string
- 布爾型,bool
- 2,復合類型
- 數組
- struct結構體
- 3,引用類型
- slice
- map
- channel
- pointer or 引用類型
- 4,接口類型
io.Reader, io.Writer,error等
一,基本類型的變量比較
golang中的基本類型
比較的兩個變量類型必須相等。而且,golang沒有隱式類型轉換,比較的兩個變量必須類型完全一樣,類型別名也不行。如果要比較,先做類型轉換再比較。
- 類型完全不一樣的,不能比較
類型再定義關系,不能比較,可以強轉比較
類型別名關系,可以比較
fmt.Println("2" == 2) //invalid operation: "2" == 2 (mismatched types string and int)
type A int
var a int = 1
var b A = 1
fmt.Println(a == b) //invalid operation: a == b (mismatched types int and A)
fmt.Println(a == int(b)) //true
type C = int
var c C = 1
fmt.Println(a == c) //true
二,復合類型的變量比較
復合類型是逐個字段,逐個元素比較的。需要注意的是,array 或者struct中每個元素必須要是可比較的,如果某個array的元素 or struct的成員不能比較(比如是后面介紹的slice,map等),則此復合類型也不能比較。
1,數組類型變量比較
- 數組的長度是類型的一部分,如果數組長度不同,無法比較
- 逐個元素比較類型和值。每個對應元素的比較遵循基本類型變量的比較規則。跟struct一樣,如果item是不可比較的類型,則array也不能做比較。
2,struct類型變量比較
逐個成員比較類型和值。每個對應成員的比較遵循基本類型變量的比較規則。
type Student struct {
Name string
Age int
}
a := Student{"minping", 30}
b := Student{"minping", 30}
fmt.Println(a == b) //true
fmt.Println(&a == &b) //false
但是如果struct中有不可比較的成員類型時:
type Student struct {
Name string
Age int
Info []string
}
a := Student{
Name: "minping",
Age: 30,
}
b := Student{
Name: "minping",
Age: 30,
}
fmt.Println(a == b) //invalid operation: a == b (struct containing []string cannot be compared)
可以看到,struct中有slice這種不可比較的成員時,整個struct都不能做比較,即使沒有對slice那個成員賦值(slice默認值為nil)
三,引用類型的變量比較
slice和map的比較規則比較奇怪,我們先說普通的變量引用類型&val和channel的比較規則。
1,普通的變量引用類型&val和channel的比較規則
引用類型變量存儲的是某個變量的內存地址。所以引用類型變量的比較,判斷的是這兩個引用類型存儲的是不是同一個變量。
- 如果是同一個變量,則內存地址肯定也一樣,則引用類型變量相等,用"=="判斷為true
- 如果不是同一個變量,則內存地址肯定不一樣,"=="結果為false
上面看起來比較廢話,但是得理解引用類型的含義。不然對判斷規則還是不清楚。
type Student struct {
Name string
Age int
}
a := &Student{"minping", 30}
b := &Student{"minping", 30}
fmt.Println(a == b) //false
c := a
fmt.Println(a == c) //true
//作為引用類型,channel和普通的&val判斷規則一致
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
ch3 := ch1
fmt.Println(ch1 == ch2) //false
fmt.Println(ch1 == ch3) //true
2,slice這種引用類型的比較
slice類型不可比較,只能與零值nil做比較。
a := []string{}
b := []string{}
fmt.Println(a == b) //invalid operation: a == b (slice can only be compared to nil)
關于slice類型不可比較的原因,后面會專門寫文章做討論。
3, map類型的比較
map類型和slice一樣,不能比較,只能與nil做比較。
四,interface{}類型變量的比較
接口類型的變量,包含該接口變量存儲的值和值的類型兩部分組成,分別稱為接口的動態類型和動態值。只有動態類型和動態值都相同時,兩個接口變量才相同:
type Person interface {
getName() string
}
type Student struct {
Name string
}
type Teacher struct {
Name string
}
func (s Student) getName() string {
return s.Name
}
func (t Teacher) getName() string {
return t.Name
}
func compare(s, t Person) bool {
return s == t
}
func main() {
s1 := Student{"minping"}
s2 := Student{"minping"}
t := Teacher{"minping"}
fmt.Println(compare(s1, s2)) //true
fmt.Println(compare(s1, t)) //false,類型不同
}
而且接口的動態類型必須要是可比較的,如果不能比較(比如slice,map),則運行時會報panic。因為編譯器在編譯時無法獲取接口的動態類型,所以編譯能通過,但是運行時直接panic:
type Person interface {
getName() string
}
type Student map[string]string
type Teacher map[string]string
func (s Student) getName() string {
return s["name"]
}
func (t Teacher) getName() string {
return t["name"]
}
func compare(s, t Person) bool {
return s == t
}
func main() {
s1 := Student{}
s1["name"] = "minping"
s2 := Student{}
s2["name"] = "minping"
fmt.Println(compare(s1, s2)) //runtime error: comparing uncomparable type main.Student
}
五,函數類型的比較
golang的func作為一等公民,也是一種類型,而且不可比較
f := func(int) int { return 1 }
g := func(int) int { return 2 }
f == g
六,slice和map的特殊比較
上面說過,map和slice是不可比較類型,但是有沒有特殊的方法來對slice和map做比較呢,有
1,[]byte類型的變量,使用工具包byte提供的函數就可以做比較
s1 := []byte{'f', 'o', 'o'}
s2 := []byte{'f', 'o', 'o'}
fmt.Println(bytes.Equal(s1, s2)) // true
s2 = []byte{'b', 'a', 'r'}
fmt.Println(bytes.Equal(s1, s2)) // false
s2 = []byte{'f', 'O', 'O'}
fmt.Println(bytes.EqualFold(s1, s2)) // true
s1 = []byte("?d?b?o")
s2 = []byte("?d?b?O")
fmt.Println(bytes.EqualFold(s1, s2)) // true
s1 = []byte{}
s2 = nil
fmt.Println(bytes.Equal(s1, s2)) // true
2,使用反射
reflect.DeepEqual函數可以用來比較兩個任意類型的變量
func DeepEqual(x, y interface{})
對map類型做比較:
m1 := map[string]int{"foo": 1, "bar": 2}
m2 := map[string]int{"foo": 1, "bar": 2}
// fmt.Println(m1 == m2) // map can only be compared to nil
fmt.Println(reflect.DeepEqual(m1, m2)) // true
m2 = map[string]int{"foo": 1, "bar": 3}
fmt.Println(reflect.DeepEqual(m1, m2)) // false
m3 := map[string]interface{}{"foo": [2]int{1,2}}
m4 := map[string]interface{}{"foo": [2]int{1,2}}
fmt.Println(reflect.DeepEqual(m3, m4)) // true
var m5 map[float64]string
fmt.Println(reflect.DeepEqual(m5, nil)) // false
fmt.Println(m5 == nil) // true
對slice類型做比較:
s := []string{"foo"}
fmt.Println(reflect.DeepEqual(s, []string{"foo"})) // true
fmt.Println(reflect.DeepEqual(s, []string{"bar"})) // false
s = nil
fmt.Println(reflect.DeepEqual(s, []string{})) // false
s = []string{}
fmt.Println(reflect.DeepEqual(s, []string{})) // true
對struct類型做比較:
type T struct {
name string
Age int
}
func main() {
t := T{"foo", 10}
fmt.Println(reflect.DeepEqual(t, T{"bar", 20})) // false
fmt.Println(reflect.DeepEqual(t, T{"bar", 10})) // false
fmt.Println(reflect.DeepEqual(t, T{"foo", 10})) // true
}
可以發現,只要變量的類型和值相同的話,reflect.DeepEqual比較的結果就為true
2,使用google的cmp包
直接看用例:
import (
"fmt"
"github.com/google/go-cmp/cmp"
)
type T struct {
Name string
Age int
City string
}
func main() {
x := T{"Micha?", 99, "London"}
y := T{"Adam", 88, "London"}
if diff := cmp.Diff(x, y); diff != "" {
fmt.Println(diff)
}
}
結果為:
main.T{
- Name: "Micha?",
+ Name: "Adam",
- Age: 99,
+ Age: 88,
City: "London",
}
五,總結
- 1,復合類型,只有每個元素(成員)可比較,而且類型和值都相等時,兩個復合元素才相等
- 2,slice,map不可比較,但是可以用reflect或者cmp包來比較
- 3,func作為golnag的一等公民,也是一個類型,也不能比較。
- 4,引用類型的比較是看指向的是不是同一個變量
- 5,類型再定義(type A string)不可比較,是兩種不同的類型
- 6,類型別名(type A = string)可比較,是同一種類型。
六,拓展知識
1, golang的類型再定義和類型別名
2,golang的slice和map為什么不可以比較
七,參考
1,https://medium.com/golangspec/equality-in-golang-ff44da79b7f1
2,https://studygolang.com/articles/19144
3,https://juejin.im/post/5d5ff27d518825637965f3f3