Go 语言标准库 —— cmp 包(通用比较函数)
🔹 概述
cmp 包是 Go 1.21 引入的标准库,提供了通用的比较函数。
主要功能:
- 泛型比较函数
cmp.Compare() - 泛型相等判断
cmp.Equal() - 适用于所有可比较类型
- 简化自定义类型的比较逻辑
特点:
- 类型安全(使用泛型)
- 代码简洁
- 性能优秀
- 替代手写比较逻辑
🔹 核心函数
比较两个值
cmp.Compare[T cmp.Ordered](x, y T) int
-
说明:
- 比较两个有序类型的值
- 返回字典序比较结果
- Go 1.21+ 新增
-
泛型约束:
T cmp.Ordered- 必须是有序类型(整数、浮点数、字符串)
-
返回值:
- -1 👉 x < y
- 0 👉 x == y
- 1 👉 x > y
-
支持的类型:
- 整数:int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr
- 浮点数:float32, float64
- 字符串:string
- 注意:不支持布尔型、复数、指针、切片、映射等
-
示例(完整)
package main import ( "cmp" "fmt" ) func main() { // 整数比较 fmt.Println(cmp.Compare(1, 2)) // -1 fmt.Println(cmp.Compare(5, 5)) // 0 fmt.Println(cmp.Compare(10, 3)) // 1 // 字符串比较 fmt.Println(cmp.Compare("apple", "banana")) // -1 fmt.Println(cmp.Compare("hello", "hello")) // 0 fmt.Println(cmp.Compare("world", "abc")) // 1 // 浮点数比较 fmt.Println(cmp.Compare(3.14, 2.71)) // 1 fmt.Println(cmp.Compare(1.5, 1.5)) // 0 } -
使用场景示例
-
排序函数
- 示例:
package main import ( "cmp" "fmt" "slices" ) func main() { // 升序排序 nums := []int{5, 2, 8, 1, 9} slices.SortFunc(nums, cmp.Compare) fmt.Println(nums) // [1 2 5 8 9] // 降序排序 slices.SortFunc(nums, func(a, b int) int { return -cmp.Compare(a, b) }) fmt.Println(nums) // [9 8 5 2 1] // 字符串排序 strs := []string{"banana", "apple", "cherry"} slices.SortFunc(strs, cmp.Compare) fmt.Println(strs) // [apple banana cherry] }
- 示例:
-
自定义类型排序
- 示例:
package main import ( "cmp" "fmt" "slices" ) type Person struct { Name string Age int } func main() { people := []Person{ {"Alice", 30}, {"Bob", 25}, {"Charlie", 35}, } // 按年龄排序 slices.SortFunc(people, func(a, b Person) int { return cmp.Compare(a.Age, b.Age) }) for _, p := range people { fmt.Printf("%s: %d\n", p.Name, p.Age) } // Bob: 25 // Alice: 30 // Charlie: 35 }
- 示例:
-
多字段排序
- 示例:
package main import ( "cmp" "fmt" "slices" ) type Product struct { Name string Price float64 Rank int } func main() { products := []Product{ {"Apple", 1.5, 2}, {"Banana", 0.8, 2}, {"Orange", 1.2, 1}, } // 先按 Rank 排序,再按 Price 排序 slices.SortFunc(products, func(a, b Product) int { if c := cmp.Compare(a.Rank, b.Rank); c != 0 { return c } return cmp.Compare(a.Price, b.Price) }) for _, p := range products { fmt.Printf("%s: $%.2f (Rank %d)\n", p.Name, p.Price, p.Rank) } }
- 示例:
-
-
注意事项
- ⚠️ 不支持复数类型(complex64, complex128)
- ⚠️ 不支持布尔类型
- ⚠️ 对于浮点数,NaN 的比较结果未定义
- ⚠️ 字符串比较基于 Unicode 码点
判断两个值是否相等
cmp.Equal[T comparable](x, y T) bool
-
说明:
- 判断两个可比较类型的值是否相等
- 等同于
x == y - Go 1.21+ 新增
-
泛型约束:
T comparable- 必须是可比较类型
-
返回值:
- true 👉 相等
- false 👉 不相等
-
支持的类型:
- 所有基本类型(整数、浮点数、字符串、布尔值)
- 指针
- 通道
- 接口
- 结构体(所有字段都可比较)
- 数组(元素类型可比较)
- 注意:不支持切片、映射、函数
-
示例(完整)
package main import ( "cmp" "fmt" ) func main() { // 基本类型 fmt.Println(cmp.Equal(5, 5)) // true fmt.Println(cmp.Equal(5, 10)) // false fmt.Println(cmp.Equal("hello", "hello")) // true fmt.Println(cmp.Equal(3.14, 3.14)) // true fmt.Println(cmp.Equal(true, true)) // true // 指针 x := 5 y := 5 z := &x w := &x fmt.Println(cmp.Equal(z, w)) // true(同一地址) // 结构体 type Point struct { X, Y int } p1 := Point{1, 2} p2 := Point{1, 2} fmt.Println(cmp.Equal(p1, p2)) // true } -
使用场景示例
-
泛型函数中的相等判断
- 示例:
package main import ( "cmp" "fmt" ) // 泛型去重函数 func Deduplicate[T comparable](slice []T) []T { if len(slice) == 0 { return slice } result := []T{slice[0]} for i := 1; i < len(slice); i++ { if !cmp.Equal(slice[i], slice[i-1]) { result = append(result, slice[i]) } } return result } func main() { nums := []int{1, 1, 2, 2, 3, 3, 3} unique := Deduplicate(nums) fmt.Println(unique) // [1 2 3] strs := []string{"a", "b", "b", "c"} uniqueStr := Deduplicate(strs) fmt.Println(uniqueStr) // [a b c] }
- 示例:
-
可选值比较
- 示例:
package main import ( "cmp" "fmt" ) func main() { var a *int var b *int // 两个 nil 指针相等 fmt.Println(cmp.Equal(a, b)) // true x := 5 a = &x fmt.Println(cmp.Equal(a, b)) // false }
- 示例:
-
结构体数组去重
- 示例:
package main import ( "cmp" "fmt" ) type User struct { ID int Name string } func main() { users := []User{ {1, "Alice"}, {1, "Alice"}, {2, "Bob"}, {2, "Bob"}, {3, "Charlie"}, } // 去重 unique := make([]User, 0) seen := make(map[User]bool) for _, u := range users { if !seen[u] { seen[u] = true unique = append(unique, u) } } fmt.Printf("去重后:%+v\n", unique) }
- 示例:
-
-
注意事项
- ⚠️ 不支持切片、映射、函数类型
- ⚠️ 对于浮点数,NaN != NaN
- ⚠️ 接口值比较时,需要动态类型和动态值都相等
- ⚠️ 包含不可比较字段的结构体不能使用此函数
🔹 cmp.Ordered 类型
有序类型约束
cmp.Ordered
-
说明:
- 预定义的泛型类型约束
- 包含所有支持
<、>、<=、>=比较的类型 - Go 1.21+ 新增
-
定义:
type Ordered interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string } -
包含的类型:
- 所有整数类型(有符号和无符号)
- 所有浮点数类型
- 字符串类型
-
不包含的类型:
- 布尔型(bool)
- 复数(complex64, complex128)
- 指针
- 切片
- 映射
- 通道
- 函数
- 接口
- 结构体
-
示例(完整)
package main import ( "cmp" "fmt" ) // 自定义泛型函数,使用 cmp.Ordered func Min[T cmp.Ordered](a, b T) T { if cmp.Compare(a, b) < 0 { return a } return b } func Max[T cmp.Ordered](a, b T) T { if cmp.Compare(a, b) > 0 { return a } return b } func main() { // 整数 fmt.Println(Min(5, 10)) // 5 fmt.Println(Max(5, 10)) // 10 // 浮点数 fmt.Println(Min(3.14, 2.71)) // 2.71 fmt.Println(Max(3.14, 2.71)) // 3.14 // 字符串 fmt.Println(Min("apple", "banana")) // apple fmt.Println(Max("apple", "banana")) // banana }
🔹 与传统比较方式对比
传统方式 vs cmp 包
-
传统方式的问题:
- 需要手写比较逻辑
- 代码冗长
- 容易出错
- 不够统一
-
cmp 包的优势:
- 统一的比较接口
- 代码简洁
- 类型安全
- 可读性强
-
对比示例
package main import ( "cmp" "fmt" "slices" ) type Person struct { Name string Age int } func main() { people := []Person{ {"Alice", 30}, {"Bob", 25}, {"Charlie", 35}, } // 传统方式 slices.SortFunc(people, func(a, b Person) int { if a.Age < b.Age { return -1 } if a.Age > b.Age { return 1 } return 0 }) // 使用 cmp 包 slices.SortFunc(people, func(a, b Person) int { return cmp.Compare(a.Age, b.Age) }) // 传统方式判断相等 if a.Age == b.Age && a.Name == b.Name { // ... } // 使用 cmp 包 if cmp.Equal(a, b) { // ... } }
🔹 实际应用场景
1. 自定义类型排序
package main
import (
"cmp"
"fmt"
"slices"
)
type Version struct {
Major int
Minor int
Patch int
}
func main() {
versions := []Version{
{1, 0, 0},
{2, 0, 0},
{1, 5, 0},
{1, 2, 3},
{1, 2, 1},
}
// 语义化版本排序
slices.SortFunc(versions, func(a, b Version) int {
if c := cmp.Compare(a.Major, b.Major); c != 0 {
return c
}
if c := cmp.Compare(a.Minor, b.Minor); c != 0 {
return c
}
return cmp.Compare(a.Patch, b.Patch)
})
for _, v := range versions {
fmt.Printf("v%d.%d.%d\n", v.Major, v.Minor, v.Patch)
}
// v1.0.0
// v1.2.1
// v1.2.3
// v1.5.0
// v2.0.0
}
2. 多条件筛选
package main
import (
"cmp"
"fmt"
)
type Employee struct {
Name string
Age int
Salary float64
Level int
}
func main() {
employees := []Employee{
{"Alice", 30, 50000, 3},
{"Bob", 25, 45000, 2},
{"Charlie", 35, 60000, 4},
}
// 查找最优员工(级别最高,工资最高,年龄最小)
best := employees[0]
for _, e := range employees[1:] {
// 先比较级别
if c := cmp.Compare(e.Level, best.Level); c > 0 {
best = e
} else if c == 0 {
// 级别相同比较工资
if c := cmp.Compare(e.Salary, best.Salary); c > 0 {
best = e
} else if c == 0 {
// 工资相同比较年龄(越小越好)
if c := cmp.Compare(e.Age, best.Age); c < 0 {
best = e
}
}
}
}
fmt.Printf("最优员工:%s\n", best.Name)
}
3. 泛型工具函数
package main
import (
"cmp"
"fmt"
)
// 泛型 Clamp 函数:限制值在指定范围内
func Clamp[T cmp.Ordered](value, min, max T) T {
if cmp.Compare(value, min) < 0 {
return min
}
if cmp.Compare(value, max) > 0 {
return max
}
return value
}
// 泛型 Between 函数:检查值是否在范围内
func Between[T cmp.Ordered](value, min, max T) bool {
return cmp.Compare(value, min) >= 0 && cmp.Compare(value, max) <= 0
}
// 泛型 Max 函数:返回最大值
func Max[T cmp.Ordered](values ...T) T {
if len(values) == 0 {
var zero T
return zero
}
max := values[0]
for _, v := range values[1:] {
if cmp.Compare(v, max) > 0 {
max = v
}
}
return max
}
// 泛型 Min 函数:返回最小值
func Min[T cmp.Ordered](values ...T) T {
if len(values) == 0 {
var zero T
return zero
}
min := values[0]
for _, v := range values[1:] {
if cmp.Compare(v, min) < 0 {
min = v
}
}
return min
}
func main() {
// Clamp 使用
fmt.Println(Clamp(5, 0, 10)) // 5
fmt.Println(Clamp(-5, 0, 10)) // 0
fmt.Println(Clamp(15, 0, 10)) // 10
// Between 使用
fmt.Println(Between(5, 0, 10)) // true
fmt.Println(Between(-5, 0, 10)) // false
// Max 使用
fmt.Println(Max(1, 5, 3, 9, 2)) // 9
fmt.Println(Max("apple", "banana", "cherry")) // cherry
// Min 使用
fmt.Println(Min(1, 5, 3, 9, 2)) // 1
fmt.Println(Min("apple", "banana", "cherry")) // apple
}
4. 与 slices 包配合使用
package main
import (
"cmp"
"fmt"
"slices"
)
func main() {
// 升序排序
nums := []int{5, 2, 8, 1, 9, 3}
slices.SortFunc(nums, cmp.Compare)
fmt.Println("升序:", nums) // [1 2 3 5 8 9]
// 降序排序
slices.SortFunc(nums, func(a, b int) int {
return -cmp.Compare(a, b)
})
fmt.Println("降序:", nums) // [9 8 5 3 2 1]
// 查找最小值
minIdx := slices.MinFunc(nums, cmp.Compare)
fmt.Println("最小值索引:", minIdx)
// 二分查找
target := 5
idx, found := slices.BinarySearchFunc(nums, target, cmp.Compare)
fmt.Printf("查找 %d: 索引=%d, 找到=%v\n", target, idx, found)
// 字符串排序
strs := []string{"banana", "apple", "cherry", "date"}
slices.SortFunc(strs, cmp.Compare)
fmt.Println("排序后:", strs)
}
5. 实现自定义比较器
package main
import (
"cmp"
"fmt"
"slices"
)
// 不区分大小写的字符串比较
func CaseInsensitiveCompare(a, b string) int {
// 转小写后比较
return cmp.Compare(a, b)
}
// 版本号比较
func CompareVersion(v1, v2 string) int {
// 简单实现,实际应解析版本号
return cmp.Compare(v1, v2)
}
func main() {
// 使用自定义比较器
versions := []string{"1.0", "2.0", "1.5", "1.10"}
slices.SortFunc(versions, CompareVersion)
fmt.Println(versions)
}
🔹 注意事项和最佳实践
1. 类型限制
- ✅ 支持:整数、浮点数、字符串
- ❌ 不支持:布尔、复数、切片、映射、函数
- ⚠️ 注意:结构体所有字段必须可比较才能使用
cmp.Equal()
2. 浮点数比较
package main
import (
"cmp"
"fmt"
"math"
)
func main() {
// NaN 的比较结果未定义
nan := math.NaN()
fmt.Println(cmp.Compare(nan, nan)) // 未定义行为
// 建议使用误差范围比较浮点数
a := 0.1 + 0.2
b := 0.3
const epsilon = 1e-9
fmt.Println(math.Abs(a-b) < epsilon) // true
}
3. 性能考虑
- cmp.Compare() 和内联的比较逻辑性能相当
- 编译器会优化泛型代码
- 在性能关键代码中,可以直接使用操作符比较
4. 代码风格建议
- ✅ 推荐:使用 cmp.Compare() 使代码更简洁
- ✅ 推荐:与 slices.SortFunc() 配合使用
- ✅ 推荐:在泛型函数中使用 cmp 包
- ⚠️ 注意:不要过度使用,简单场景直接用操作符即可
🔥 总结
核心函数
| 函数 | 说明 | 约束 | 返回值 |
|---|---|---|---|
cmp.Compare(x, y) | 比较两个值 | cmp.Ordered | -1, 0, 1 |
cmp.Equal(x, y) | 判断是否相等 | comparable | bool |
支持的类型
cmp.Ordered(有序类型):
- 整数:int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr
- 浮点数:float32, float64
- 字符串:string
comparable(可比较类型):
- 所有基本类型(包括 bool)
- 指针、通道、接口
- 可比较字段的结构体
- 元素可比较的数组
主要优势
- 简洁性 👉 替代冗长的比较逻辑
- 类型安全 👉 泛型确保类型正确
- 统一接口 👉 所有类型使用相同的比较方式
- 可读性 👉 代码意图更清晰
- 可维护性 👉 减少重复代码
常见使用场景
- 排序 👉 与 slices.SortFunc() 配合使用
- 泛型函数 👉 编写通用的比较、查找、排序函数
- 多字段比较 👉 链式比较多个字段
- 自定义类型 👉 简化结构体比较逻辑
- 工具函数 👉 Min、Max、Clamp、Between 等
与 slices 包配合
import (
"cmp"
"slices"
)
// 排序
slices.SortFunc(slice, cmp.Compare)
// 查找最小值
slices.MinFunc(slice, cmp.Compare)
// 二分查找
slices.BinarySearchFunc(slice, target, cmp.Compare)
最佳实践
- ✅ 在泛型代码中优先使用 cmp.Compare()
- ✅ 使用 cmp.Equal() 替代 == 提高可读性
- ✅ 多字段排序时使用链式比较
- ✅ 与 slices 包配合使用
- ⚠️ 浮点数比较注意精度问题
- ⚠️ 了解类型限制,避免编译错误
- ⚠️ 简单场景不需要过度使用
Go 版本要求
- 最低版本 👉 Go 1.21
- 泛型支持 👉 Go 1.18+(但 cmp 包是 1.21 引入)
cmp 包是现代 Go 泛型编程的重要工具,让比较操作更加简洁、安全和统一!