ZetCode

Golang 可比较内建类型

最后修改时间 2025 年 5 月 8 日

本教程将解释如何在 Go 中使用 `comparable` 内建类型。我们将通过使用 comparable 的泛型实用示例来介绍类型约束。

`comparable` 类型是 Go 中一个预先声明的接口,它表示所有可以使用 `==` 和 `!=` 运算符进行比较的类型。它是在 Go 1.18 中引入的,用于支持泛型。

在 Go 中,`comparable` 被用作类型约束,以确保类型参数支持相等性操作。这对于编写需要比较值的泛型函数至关重要。

基本的 comparable 用法

`comparable` 最简单的用法是将类型参数约束为支持相等性操作。此示例演示了基本的 comparable 用法。
注意: comparable 包括除接口外的所有可比较类型。

basic_comparable.go
package main

import "fmt"

func Equal[T comparable](a, b T) bool {
    return a == b
}

func main() {

    fmt.Println(Equal(5, 5))       // true
    fmt.Println(Equal("foo", "bar")) // false
    fmt.Println(Equal(3.14, 3.14))  // true
}

`Equal` 函数可用于任何可比较类型。它演示了 comparable 如何在泛型中实现类型安全的相等性比较。

将 comparable 与结构体一起使用

如果结构体类型的所有字段都可比较,那么该结构体类型就可比较。此示例展示了 comparable 如何与自定义结构体类型一起使用。

struct_comparable.go
package main

import "fmt"

type Point struct {
    X, Y int
}

func Contains[T comparable](slice []T, value T) bool {
    for _, v := range slice {
        if v == value {
            return true
        }
    }
    return false
}

func main() {

    points := []Point{{1, 2}, {3, 4}, {5, 6}}
    fmt.Println(Contains(points, Point{3, 4})) // true
    fmt.Println(Contains(points, Point{7, 8})) // false
}

`Contains` 函数可用于任何可比较类型,包括结构体。Point 结构体是可比较的,因为它的字段是可比较的。

将 comparable 与映射一起使用

映射键必须是可比较类型。此示例展示了 comparable 如何帮助创建通用的映射实用工具。

map_comparable.go
package main

import "fmt"

func MapKeys[K comparable, V any](m map[K]V) []K {
    keys := make([]K, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }
    return keys
}

func main() {

    strMap := map[string]int{"a": 1, "b": 2}
    fmt.Println(MapKeys(strMap)) // [a b]

    intMap := map[int]string{1: "one", 2: "two"}
    fmt.Println(MapKeys(intMap)) // [1 2]
}

`MapKeys` 函数从任何映射类型中提取键。K 类型参数必须是可比较的,因为映射键需要比较操作。

Comparable 的限制

并非所有类型都可比较。此示例演示了不满足 comparable 约束的类型以及如何处理它们。

limitations.go
package main

import "fmt"

type NonComparable struct {
    Data []int
}

func main() {

    // This would cause a compile-time error:
    // fmt.Println(Equal(NonComparable{}, NonComparable{}))
    
    // Workaround for non-comparable types
    compareSlices := func(a, b []int) bool {
        if len(a) != len(b) {
            return false
        }
        for i := range a {
            if a[i] != b[i] {
                return false
            }
        }
        return true
    }
    
    nc1 := NonComparable{Data: []int{1, 2, 3}}
    nc2 := NonComparable{Data: []int{1, 2, 3}}
    fmt.Println(compareSlices(nc1.Data, nc2.Data)) // true
}

切片、映射和函数是不可比较的。该示例展示了如何通过自定义比较逻辑来解决此限制。

将 comparable 与接口一起使用

如果接口的动态类型是可比较的,那么该接口就是可比较的。此示例演示了 comparable 与接口类型的行为。

interface_comparable.go
package main

import "fmt"

type Stringer interface {
    String() string
}

type MyString string

func (ms MyString) String() string {
    return string(ms)
}

func CompareStringers[T Stringer](a, b T) bool {
    return a == b
}

func main() {

    var s1 Stringer = MyString("hello")
    var s2 Stringer = MyString("hello")
    var s3 Stringer = MyString("world")
    
    fmt.Println(CompareStringers(s1, s2)) // true
    fmt.Println(CompareStringers(s1, s3)) // false
}

`CompareStringers` 函数之所以有效,是因为底层类型(MyString)是可比较的。接口比较取决于它们的动态类型。

来源

Go 语言规范

本教程通过在泛型函数和类型约束中使用 comparable 的实用示例,涵盖了 Go 语言中的 comparable 内建类型。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。至今,我已撰写了 1400 多篇文章和 8 本电子书。我在教学编程方面拥有十多年的经验。

列出所有 Golang 教程