怎么让 Go 中如何让结构体不可比较?

本文涉及的产品
云原生数据库 PolarDB PostgreSQL 版,企业版 4核16GB
推荐场景:
HTAP混合负载
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 在 Go 语言中,结构体默认是可以比较的,只要它们的所有字段都是可比较的。如果结构体包含不可比较的字段(如 `map`),则该结构体不能直接使用 `==` 进行比较,会引发编译错误。可以通过 `reflect.DeepEqual()` 函数对包含不可比较字段的结构体进行深度比较。为了禁止结构体的相等性比较,可以使用 `_ [0]func()` 作为匿名字段,这是一种不占用额外内存且具有明确语义的优雅做法。Go 语言标准库中也有类似的应用。

在 Go 中结构体可以比较吗?

在 Go 中结构体可以比较吗?这其实是我曾经面试过的一个问题,我们来做一个实验:

定义如下结构体:

go

复制代码

type Normal struct {
	a string
	B int
}

使用这个结构体分别声明 3 个变量 n1n2n3,然后进行比较:

go

复制代码

n1 := Normal{
	a: "a",
	B: 10,
}
n2 := Normal{
	a: "a",
	B: 10,
}
n3 := Normal{
	a: "b",
	B: 20,
}

fmt.Println(n1 == n2)
fmt.Println(n1 == n3)

执行示例代码,输出结果如下:

bash

复制代码

$ go run main.go
true
false

可见 Normal 结构体是可以比较的。

如何让结构体不可比较?

那么所有结构体都可以比较吗?显然不是,如果都可以比较,那么 reflect.DeepEqual() 就没有存在的必要了。

定义如下结构体:

go

复制代码

type NoCompare struct {
	a string
	B map[string]int
}

使用这个结构体分别声明 2 个变量 n1n2,然后进行比较:

go

复制代码

n1 := NoCompare{
	a: "a",
	B: map[string]int{
		"a": 10,
	},
}
n2 := NoCompare{
	a: "a",
	B: map[string]int{
		"a": 10,
	},
}

fmt.Println(n1 == n2)

执行示例代码,输出结果如下:

bash

复制代码

$ go run main.go
./main.go:59:15: invalid operation: n1 == n2 (struct containing map[string]int cannot be compared)

这里程序直接报错了,并提示结构体包含了 map[string]int 类型字段,不可比较。

所以小结一下:

结构体是否可以比较,不取决于字段是否可导出,而是取决于其是否包含不可比较字段。

如果全部字段都是可比较的,那么这个结构体就是可比较的。

如果其中有一个字段不可比较,那么这个结构体就是不可比较的。

不过虽然我们不可以使用 ==n1n2 进行比较,但我们可以使用 reflect.DeepEqual() 对二者进行比较:

go

复制代码

fmt.Println(reflect.DeepEqual(n1, n2))

执行示例代码,输出结果如下:

bash

复制代码

$ go run main.go
true

更优雅的做法

最近我在使用 Go 官方出品的结构化日志包 slog 时,看到 slog.Value 源码:

go

复制代码

// A Value can represent any Go value, but unlike type any,
// it can represent most small values without an allocation.
// The zero Value corresponds to nil.
type Value struct {
	_ [0]func() // disallow ==
	// num holds the value for Kinds Int64, Uint64, Float64, Bool and Duration,
	// the string length for KindString, and nanoseconds since the epoch for KindTime.
	num uint64
	// If any is of type Kind, then the value is in num as described above.
	// If any is of type *time.Location, then the Kind is Time and time.Time value
	// can be constructed from the Unix nanos in num and the location (monotonic time
	// is not preserved).
	// If any is of type stringptr, then the Kind is String and the string value
	// consists of the length in num and the pointer in any.
	// Otherwise, the Kind is Any and any is the value.
	// (This implies that Attrs cannot store values of type Kind, *time.Location
	// or stringptr.)
	any any
}

可以发现,这里有一个匿名字段 _ [0]func(),并且注释写着 // disallow ==

_ [0]func() 的目的显然是为了禁止比较。

我们来实验一下,_ [0]func() 是否能够实现禁止结构体相等性比较:

go

复制代码

v1 := Value{
	num: 1,
	any: 2,
}
v2 := Value{
	num: 1,
	any: 2,
}

fmt.Println(v1 == v2)

执行示例代码,输出结果如下:

bash

复制代码

$ go run main.go
./main.go:109:15: invalid operation: v1 == v2 (struct containing [0]func() cannot be compared)

可以发现,的确有效。因为 func() 是一个函数,而函数在 Go 中是不可比较的。

既然使用 map[string]int_ [0]func() 都能实现禁止结构体相等性比较,那么我为什么说 _ [0]func() 是更优雅的做法呢?

_ [0]func() 有着比其他实现方式更优的特点:

它不占内存空间!

使用匿名字段 _ 语义也更强。

而且,我们直接去 Go 源码里搜索,能够发现其实 Go 本身也在多处使用了这种用法:

所以推荐使用 _ [0]func() 来实现禁用结构体相等性比较。

转载来源:https://juejin.cn/post/7381396887189569575

相关文章
|
2月前
|
XML JSON Go
掌握Go语言:Go语言结构体进阶,探索高级特性与实用技巧(23)
掌握Go语言:Go语言结构体进阶,探索高级特性与实用技巧(23)
|
2月前
|
Go 开发者
掌握Go语言:Go语言结构体,精准封装数据,高效管理实体对象(22)
掌握Go语言:Go语言结构体,精准封装数据,高效管理实体对象(22)
|
2月前
|
关系型数据库 MySQL Go
工厂模式+自动注册管理Go多包结构体
工厂模式+自动注册管理Go多包结构体
94 1
|
29天前
|
Go
go结构体的定义
go结构体的定义
15 1
|
29天前
|
Go
go反射获取变量类型、值、结构体成员、结构体方法
go反射获取变量类型、值、结构体成员、结构体方法
21 0
|
2月前
|
存储 JSON 缓存
Go语言学习9-结构体类型
【4月更文挑战第8天】本篇 Huazie 向大家介绍 Go语言的接口类型
309 8
Go语言学习9-结构体类型
|
2月前
|
存储 Go Python
Go 语言结构体
Go 语言结构体
13 0
|
2月前
|
Go 开发者
Golang深入浅出之-Go语言结构体(struct)入门:定义与使用
【4月更文挑战第22天】Go语言中的结构体是构建复杂数据类型的关键,允许组合多种字段。本文探讨了结构体定义、使用及常见问题。结构体定义如`type Person struct { Name string; Age int; Address Address }`。问题包括未初始化字段的默认值、比较含不可比较字段的结构体以及嵌入结构体字段重名。避免方法包括初始化结构体、自定义比较逻辑和使用明确字段选择器。结构体方法、指针接收者和匿名字段嵌入提供了灵活性。理解这些问题和解决策略能提升Go语言编程的效率和代码质量。
45 1
|
2月前
|
存储 Go 开发者
【Go语言专栏】Go语言中的结构体与方法
【4月更文挑战第30天】Go语言中的结构体是聚合数据类型,用于自定义复杂类型。通过`type`和`struct`关键字定义结构体,包含多个不同类型的字段。结构体实例化后,使用点操作符访问字段。方法为结构体添加行为,定义时需指定接收者(值或指针)。方法调用同样使用点操作符。匿名结构体无需命名,嵌套结构体可构建复杂数据结构。选择值或指针接收者取决于是否需要修改结构体状态。理解并熟练运用结构体和方法是编写高效Go代码的关键。
|
2月前
|
编译器 Go
go语言第六章(结构体与方法)
go语言第六章(结构体与方法)
29 0