掘金 后端 ( ) • 2024-05-10 13:28

highlight: zenburn theme: condensed-night-purple

在 Go 语言中,interface{} 类型是一种特殊的接口类型,它表示任意类型的值。你可以使用 == 运算符来检测任意两个 interface{} 类型值的相等性,比较的规则和一般的接口类型一样,需要满足以下条件:

  • 两个 interface{} 值的动态类型必须相同,也就是说,它们存储的具体类型必须一致。
  • 两个 interface{} 值的动态值必须相等,也就是说,它们存储的具体值必须可以比较的。

根据 Go 语言规范的描述,interface{} 类型的值在内存中是由两个字(word)组成的,一个字存储了动态类型的信息,另一个字存储了动态值的信息。如果动态值的大小超过了一个字,那么这个字就存储了动态值的指针,指向实际的动态值。

Go 编译器是如何判定 interface{} 值是否相等的呢?

使用 == 符号比较两个 interface{} 类型的值时,Go编译器会先比较它们的动态类型,也就是比较它们的第一个字,看是否指向相同的类型信息。如果不相同直接返回 false,如果相同,则继续比较它们的动态值,也就是比较它们的第二个字,看是否相等。如果动态值的大小超过了一个字,那么这个字就是一个指针,需要根据指针找到实际的动态值,并且根据动态类型的规则进行比较。

例如,如果动态类型是数组,那么就需要逐个比较数组的元素,如果动态类型是结构体,那么就需要逐个比较结构体的字段,以此类推。

下面是一个简单的例子,演示了如何使用 == 符号比较两个 interface{} 类型的值:

package main

import "fmt"

func main() {
	var a, b interface{}
	// 给 a 赋值为 int64 类型的 1
	a = int64(1)
	// 给 b 赋值为 int64 类型的 2
	b = int64(2)
	// a和b的动态类型相同, 动态值不同, 输出结果为 false
	fmt.Println(a == b) // false

	// 给 b 赋值为 int64 类型的 1
	b = int64(1)
	// a和b的动态类型相同, 动态值也相同, 此时输出结果为true
	fmt.Println(a == b) // true

	// 给 b 赋值为 float32 类型的 1
	b = float32(1)
	// 此时a和b的动态类型不相同, 就不会进行动态值的处理, 输出结果为false
	fmt.Println(a == b) // false

	// 给 a,b 赋值为 int 类型的数组
	a = [5]int{1, 2, 3, 4, 5}
	b = [5]int{1, 2, 3, 4, 5}
	fmt.Println(a == b) // true

	// 给 b 赋值为 int 类型的数组
	b = [5]int{1, 2, 3, 4, 6}
	fmt.Println(a == b) // false

	// 给 b 赋值为 int 类型的切片
	b = []int{1, 2, 3, 4, 5}
	// 此时a和b的动态类型已然不相同, 就不会进行动态值的处理, 输出结果为false
	fmt.Println(a == b) // false

	// 给 a 赋值为 int 类型的切片
	a = []int{1, 2, 3, 4, 5}
	// 此时a和b的动态类型和动态值虽然相同, 但是在Go语言中切片类型的值是不能用 == 符号比较,会引发运行时错误
	//fmt.Println(a == b) // panic: runtime error: comparing uncomparable type []int
}

== 运算符通常只能用于比较基本类型和支持 == 操作的复合类型,如数组、结构体等,而不能用于比较切片、映射、函数等类型,否则会引发编译错误或运行时错误。

那么问题来了,如果一定要比较两个切片、映射、函数的相等性,该如何操作呢?

reflect.DeepEqual 函数原型:

// 用于判断两个值是否深度一致
// 
// 除了类型相同;在可以时(主要是基本类型)会使用==;但还会比较array、slice的成员,
// map的键值对,结构体字段进行深入比对。map的键值对,对键只使用==,但值会继续往深
// 层比对。DeepEqual函数可以正确处理循环的类型。函数类型只有都会nil时才相等;空切
// 片不等于nil切片;还会考虑array、slice的长度、map键值对数。
func DeepEqual(x, y any) bool

对于 reflect.DeepEqual 而言,它通过牺牲程序的性能来弥补 == 运算符无法处理切片、映射、函数的短板, 对于不支持 == 操作的类型,reflect.DeepEqual 函数会有一套自己的比较规则。

你可以把 reflect.DeepEqual 函数理解成是 == 运算符的扩展版!下面是一个简单的例子,演示了如何使用 reflect.DeepEqual 函数:

package main

import "fmt"

func main() {
	var a, b interface{}
	// 给 a 赋值为 int64 类型的 1
	a = int64(1)
	// 给 b 赋值为 int64 类型的 2
	b = int64(2)
	fmt.Println(a == b, reflect.DeepEqual(a, b))

	b = int64(1)
	fmt.Println(a == b, reflect.DeepEqual(a, b))

	b = float32(1)
	fmt.Println(a == b, reflect.DeepEqual(a, b))

	// 给 b 赋值为 int 类型的切片
	a, b = []int{1, 2, 3, 4, 5}, []int{1, 2, 3, 4, 5}
	fmt.Println(reflect.DeepEqual(a, b)) // true

	b = []int{5, 4, 3, 2, 1}
	fmt.Println(reflect.DeepEqual(a, b)) // false
}