背景交代
大家經(jīng)常用"=="來(lái)比較兩個(gè)變量是否相等勇皇。但是golang中的"=="有很多細(xì)節(jié)的地方轰坊,跟php是不一樣的冀宴。很多時(shí)候不能直接用"=="來(lái)比較,編譯器會(huì)直接報(bào)錯(cuò)。
golang中基本類型的比較規(guī)則和復(fù)合類型的不一致聪建,先介紹下golang的變量類型:
- 1钙畔,基本類型
- 整型,包括int金麸,uint擎析,int8,uint8挥下,int16揍魂,uint16,int32棚瘟,uint32现斋,int64,uint64偎蘸,byte庄蹋,rune,uintptr等
- 浮點(diǎn)型迷雪,包括float32限书,float64
- 復(fù)數(shù)類型,包括complex64振乏,complex128
- 字符串類型蔗包,string
- 布爾型,bool
- 2慧邮,復(fù)合類型
- 數(shù)組
- struct結(jié)構(gòu)體
- 3调限,引用類型
- slice
- map
- channel
- pointer or 引用類型
- 4,接口類型
io.Reader, io.Writer,error等
一误澳,基本類型的變量比較
golang中的基本類型
比較的兩個(gè)變量類型必須相等耻矮。而且,golang沒(méi)有隱式類型轉(zhuǎn)換忆谓,比較的兩個(gè)變量必須類型完全一樣裆装,類型別名也不行。如果要比較倡缠,先做類型轉(zhuǎn)換再比較哨免。
- 類型完全不一樣的,不能比較
類型再定義關(guān)系昙沦,不能比較琢唾,可以強(qiáng)轉(zhuǎn)比較
類型別名關(guān)系,可以比較
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
二盾饮,復(fù)合類型的變量比較
復(fù)合類型是逐個(gè)字段采桃,逐個(gè)元素比較的懒熙。需要注意的是,array 或者struct中每個(gè)元素必須要是可比較的普办,如果某個(gè)array的元素 or struct的成員不能比較(比如是后面介紹的slice工扎,map等),則此復(fù)合類型也不能比較衔蹲。
1肢娘,數(shù)組類型變量比較
- 數(shù)組的長(zhǎng)度是類型的一部分,如果數(shù)組長(zhǎng)度不同踪危,無(wú)法比較
- 逐個(gè)元素比較類型和值蔬浙。每個(gè)對(duì)應(yīng)元素的比較遵循基本類型變量的比較規(guī)則猪落。跟struct一樣贞远,如果item是不可比較的類型,則array也不能做比較笨忌。
2蓝仲,struct類型變量比較
逐個(gè)成員比較類型和值。每個(gè)對(duì)應(yīng)成員的比較遵循基本類型變量的比較規(guī)則官疲。
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中有不可比較的成員類型時(shí):
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這種不可比較的成員時(shí),整個(gè)struct都不能做比較途凫,即使沒(méi)有對(duì)slice那個(gè)成員賦值(slice默認(rèn)值為nil)
三垢夹,引用類型的變量比較
slice和map的比較規(guī)則比較奇怪,我們先說(shuō)普通的變量引用類型&val和channel的比較規(guī)則维费。
1果元,普通的變量引用類型&val和channel的比較規(guī)則
引用類型變量存儲(chǔ)的是某個(gè)變量的內(nèi)存地址。所以引用類型變量的比較犀盟,判斷的是這兩個(gè)引用類型存儲(chǔ)的是不是同一個(gè)變量而晒。
- 如果是同一個(gè)變量,則內(nèi)存地址肯定也一樣阅畴,則引用類型變量相等倡怎,用"=="判斷為true
- 如果不是同一個(gè)變量,則內(nèi)存地址肯定不一樣贱枣,"=="結(jié)果為false
上面看起來(lái)比較廢話监署,但是得理解引用類型的含義。不然對(duì)判斷規(guī)則還是不清楚纽哥。
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判斷規(guī)則一致
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)
關(guān)于slice類型不可比較的原因累魔,后面會(huì)專門寫文章做討論。
3够滑, map類型的比較
map類型和slice一樣垦写,不能比較,只能與nil做比較彰触。
四梯投,interface{}類型變量的比較
接口類型的變量,包含該接口變量存儲(chǔ)的值和值的類型兩部分組成况毅,分別稱為接口的動(dòng)態(tài)類型和動(dòng)態(tài)值分蓖。只有動(dòng)態(tài)類型和動(dòng)態(tài)值都相同時(shí),兩個(gè)接口變量才相同:
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,類型不同
}
而且接口的動(dòng)態(tài)類型必須要是可比較的尔许,如果不能比較(比如slice么鹤,map),則運(yùn)行時(shí)會(huì)報(bào)panic味廊。因?yàn)榫幾g器在編譯時(shí)無(wú)法獲取接口的動(dòng)態(tài)類型蒸甜,所以編譯能通過(guò),但是運(yùn)行時(shí)直接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
}
五余佛,函數(shù)類型的比較
golang的func作為一等公民柠新,也是一種類型,而且不可比較
f := func(int) int { return 1 }
g := func(int) int { return 2 }
f == g
六辉巡,slice和map的特殊比較
上面說(shuō)過(guò)恨憎,map和slice是不可比較類型,但是有沒(méi)有特殊的方法來(lái)對(duì)slice和map做比較呢郊楣,有
1憔恳,[]byte類型的變量,使用工具包byte提供的函數(shù)就可以做比較
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函數(shù)可以用來(lái)比較兩個(gè)任意類型的變量
func DeepEqual(x, y interface{})
對(duì)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
對(duì)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
對(duì)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
}
可以發(fā)現(xiàn)喇嘱,只要變量的類型和值相同的話,reflect.DeepEqual比較的結(jié)果就為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)
}
}
結(jié)果為:
main.T{
- Name: "Micha?",
+ Name: "Adam",
- Age: 99,
+ Age: 88,
City: "London",
}
五者铜,總結(jié)
- 1,復(fù)合類型放椰,只有每個(gè)元素(成員)可比較作烟,而且類型和值都相等時(shí),兩個(gè)復(fù)合元素才相等
- 2砾医,slice拿撩,map不可比較,但是可以用reflect或者cmp包來(lái)比較
- 3如蚜,func作為golnag的一等公民压恒,也是一個(gè)類型影暴,也不能比較。
- 4探赫,引用類型的比較是看指向的是不是同一個(gè)變量
- 5型宙,類型再定義(type A string)不可比較,是兩種不同的類型
- 6伦吠,類型別名(type A = string)可比較妆兑,是同一種類型。
六毛仪,拓展知識(shí)
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