【golang】结构体可以进行比较吗?

结构体可以进行比较吗?

先说结论:

在结构体字段包括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
0x519540

true

看到答案后是不是突然傻眼了,怎么跟我原先想的完全不一样呢?为什么把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了。

参考文献:

煎鱼带你学golang

用 Go struct 不能犯的一个低级错误!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

捶捶自己

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值