Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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)判断是否相等comparablebool

支持的类型

cmp.Ordered(有序类型):

  • 整数:int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr
  • 浮点数:float32, float64
  • 字符串:string

comparable(可比较类型):

  • 所有基本类型(包括 bool)
  • 指针、通道、接口
  • 可比较字段的结构体
  • 元素可比较的数组

主要优势

  • 简洁性 👉 替代冗长的比较逻辑
  • 类型安全 👉 泛型确保类型正确
  • 统一接口 👉 所有类型使用相同的比较方式
  • 可读性 👉 代码意图更清晰
  • 可维护性 👉 减少重复代码

常见使用场景

  1. 排序 👉 与 slices.SortFunc() 配合使用
  2. 泛型函数 👉 编写通用的比较、查找、排序函数
  3. 多字段比较 👉 链式比较多个字段
  4. 自定义类型 👉 简化结构体比较逻辑
  5. 工具函数 👉 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 泛型编程的重要工具,让比较操作更加简洁、安全和统一!