结构体可以进行比较吗?
先说结论:
在结构体字段包括slice
,map
,channel
这三种不能比较的类型时,结构体无法进行比较。
不同的结构体之间无法进行比较。
结论验证:
第一条很显然也很合理。那么第二条结论会发生什么事情呢?
type Value1 struct {
Name string
}
type Value2 struct {
Name string
}
func main() {
v1 := Value1{Name: "张三"}
v2 := Value2{Name: "张三"}
if v1 == v2 {
fmt.Println("张三等于张三")
return
}
fmt.Println("张三不等于张三")
}
答案输出:
# command-line-arguments
./main.go:18:8: invalid operation: v1 == v2 (mismatched types Value1 and Value2)
引入思考:
不同结构体之间是因为类型不同导致无法比较,那么相同结构体之间是否可以进行比较呢?
type Value struct {
Name string
Gender string
}
func main() {
v1 := Value{Name: "张三", Gender: "男"}
v2 := Value{Name: "张三", Gender: "男"}
if v1 == v2 {
fmt.Println("张三等于张三")
return
}
fmt.Println("张三不等于张三")
}
答案输出:
张三等于张三
那么是否所有相同的结构体都可以进行比较呢?
type Value struct {
Name string
Gender *string
}
func main() {
v1 := Value{Name: "张三", Gender: new(string)}
v2 := Value{Name: "张三", Gender: new(string)}
if v1 == v2 {
fmt.Println("张三等于张三")
return
}
fmt.Println("张三不等于张三")
}
答案输出:
张三不等于张三
所有相同的结构体,在不包括引用类型的三个字段前提下,是可以进行比较的。但是比较结果会根据类型值的不同而不同。这里不相等的原因是因为两个字段值的地址不同。
包含引用类型的结构体如何比较?
使用reflect
包下的DeepEqual
函数可以进行比较:
func main() {
v1 := Value{Name: "张三", GoodAt: []string{"唱", "跳", "rap"}}
v2 := Value{Name: "张三", GoodAt: []string{"唱", "跳", "rap"}}
if reflect.DeepEqual(v1, v2) {
fmt.Println("张三等于张三")
return
}
fmt.Println("张三不等于张三")
}
例子中所用到的反射比较方法 reflect.DeepEqual
常用于判定两个值是否深度一致,其规则如下:
- 相同类型的值是深度相等的,不同类型的值永远不会深度相等。
- 当数组值(array)的对应元素深度相等时,数组值是深度相等的。
- 当结构体(struct)值如果其对应的字段(包括导出和未导出的字段)都是深度相等的,则该值是深度相等的。
- 当函数(func)值如果都是零,则是深度相等;否则就不是深度相等。
- 当接口(interface)值如果持有深度相等的具体值,则深度相等。
- …
指针结构体如何比较?
当我们对结构体进行比较的时候,我们会发现字段值相同的结构体是相等的。那么指针类型的结构体是不是相等的呢?
type People struct {}
func main() {
a := &People{}
b := &People{}
fmt.Println(a == b)
}
这时候有人肯定会说,这两个结构体取地址后明显不是指向同一块内存,地址自然也不相同,怎么可能会相等呢?答案肯定是false。
答案输出:
false
这里先不给出结论,我们对这段代码进行一些改进后再输出:
type People struct {}
func main() {
a := &People{}
b := &People{}
fmt.Printf("%p\n", a)
fmt.Printf("%p\n", b)
fmt.Println(a == b)
}
答案输出:
0x519540
0x519540true
看到答案后是不是突然傻眼了,怎么跟我原先想的完全不一样呢?为什么把a,b两个结构体指针地址打印出来就改变了结果呢?会不会是打印之后地址改变了的原因呢?
带着这份疑惑,我们做最后一步更加细致改进。
func main() {
a := new(struct{})
b := new(struct{})
println(a, b, a == b)
c := new(struct{})
d := new(struct{})
fmt.Println(c, d)
println(c, d, c == d)
}
答案输出:
0xc0000c7f4f 0xc0000c7f4f false
&{} &{}
0x1057540 0x1057540 true
由此对比之下的结果可以看出来,在fmt.Println
函数调用之后结果发生了改变。那么这是为什么呢?
其实是因为golang的内存逃逸机制,导致这一现象的发生。fmt.Println()
函数底层使用了反射,通过reflect.TypeOf().Kind()
获取数据类型,从而触发了内存逃逸。
什么是内存逃逸呢?
简单来说就是局部变量被其他函数所捕获,那么这个局部变量就会发生内存逃逸,从栈中逃逸到堆中。
为什么逃逸后的结构体相等?
在golang的runtime包中进行了优化。所有经过逃逸后的0字节都会指向同一个地方:zerobase
。
type zerobase uintptr
因此这两个逃逸的结构体自然而然就相等了。
为什么没有逃逸的结构体不相等?
根据go语言官方写的文章看出,(这里想看文档的可以点下方的链接,看煎鱼大佬写的原版文章。)golang是有意为之,和map的无序操作一样,没有逃逸的结构体是不相等的。
你认为结构体是通过==
进行比较,实际上根本就没有比较,当编译过程中语法检测到两个空的指针结构体进行比较时,就直接默认它的答案为false了。