Go 语言中那些对象比较方式

231 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 11 天,点击查看活动详情

在 Go 中有不同的方法可以比较对象,但各有千秋。接下来,就让我们一起来看看这些对象比较的方式吧。

操作符 == 和 !=

相等运算符是 Go 中比较事物的最简单且通常最有效的方法,但它仅适用于基本方式:使用 == 您可以比较基本类型,如 int 和 string,以及其中包含可以使用 == 进行比较的元素的数组和结构。

尤其注意的是,它不适用于切片或映射。切片和映射只能与 nil 进行比较。

package main

import "fmt"

type compareStruct1 struct {
	A int
	B string
	C [5]int
}

func main() {

	a := "Hello"
	b := "Hello"
	fmt.Println("a == b is", a == b)

	s1 := compareStruct1{}
	s2 := compareStruct1{}
	fmt.Println("s1 == s2 is", s1 == s2)
}

结果:

a == b is true
s1 == s2 is true

一旦您向结构中添加了无法与 == 比较的属性,您就需要另一种比较方式:/

package main

import "fmt"

type compareStruct2 struct {
	A int
	B string
	C []int // changed type of C from array to slice
}

func main() {
	s1 := compareStruct2{}
	s2 := compareStruct2{}
        
	fmt.Println(s1 == s2)
}

结果为:

invalid operation: s1 == s2 (struct containing []int cannot be compared)

如果此时,还想进行比较,就需要编写专门的比较程序.

编写专门的代码

如果性能很重要,并且您需要比较稍微复杂的类型,那么最好的选择可能是手动比较:

package main

import "fmt"

type compareStruct struct {
	A int
	B string
	C []int
}

func (s *compareStruct) Equals(s2 *compareStruct) bool {
	if s.A != s2.A || s.B != s2.B || len(s.C) != len(s2.C) {
		return false
	}

	for i := 0; i < len(s.C); i++ {
		if s.C[i] != s2.C[i] {
			return false
		}
	}

	return true
}

func main() {
	s1 := compareStruct{}
	s2 := compareStruct{}

	fmt.Println(s1.Equals(&s2))
}

结果:true

reflect.DeepEqual

DeepEqual 是 Go 中最通用的比较事物的方法,它可以处理大部分事物。这是问题所在:

var (
    c1 = compareStruct{
        A: 1,
        B: "hello",
        C: []int{1, 2, 3},
    }
    c2 = compareStruct{
        A: 1,
        B: "hello",
        C: []int{1, 2, 3},
    }
)

func BenchmarkManual(b *testing.B) {
    for i := 0; i < b.N; i++ {
        c1.Equals(&c2)
    }
}

func BenchmarkDeepEqual(b *testing.B) {
    for i := 0; i < b.N; i++ {
        reflect.DeepEqual(c1, c2)
    }
}
BenchmarkManual-8 217182776 5.51 ns/op 0 B/op 0 allocs/op
BenchmarkDeepEqual-8 2175002 559 ns/op 144 B/op 8 allocs/op

在这个例子中,DeepEqual 比编写手动代码来比较该结构慢 100 倍。

请注意,DeepEqual 也会比较结构中未导出(小写)的字段。此外,即使它们是具有相同字段和值的两个不同结构,也永远不会认为两种不同类型是完全相等的。

不能比较类型

有些类型无法比较,甚至被认为是不相等的,例如具有 NaN 值或 func 类型的浮点变量。例如,如果您在结构中有这样的字段,则该结构不会是 DeepEqual 自身:

func TestF(t *testing.T) {
	x := math.NaN
	fmt.Println(reflect.DeepEqual(x, x))         // false
	fmt.Println(reflect.DeepEqual(TestF, TestF)) // false
}

bytes.Equal

bytes.Equal 是一种比较字节切片的特殊方法。它比简单地用 for 循环比较两个切片要快得多。

值得注意的是, bytes.Equal 函数认为空切片和 nil 切片是相等的,而 reflect.DeepEqual 则不然。