go泛型

212 阅读4分钟

在 Go 语言中,泛型(Generics)是一种支持代码复用和类型安全的功能,它允许在不同类型的数据上进行操作,而无需为每种类型编写单独的代码。Go 1.18 版本开始引入了对泛型的支持。

泛型****基础

  • 类型参数:类型参数是在函数或类型声明中定义的,使用方括号([])包围,表示泛型中可变的类型。Go需要显式约束,比如any或具体类型组合。例如,可以定义一个泛型函数 PrintSlice 如下:

  •   func PrintSlice[T any](s []T) { for _, v := range s { fmt.Println(v) } }
    

这里,T 是类型参数,any 是一个预定义的约束,表示任何类型。

  • 类型约束:类型约束定义了类型参数所允许的类型范围。Go 1.18 引入了 constraints 包,以便定义自定义约束。当没有指定约束时,可以使用预定义的约束 any,表示可以是任何类型。以下是一个使用内置约束的示例:

  •   package main
      import "constraints"
      func Max[T constraints.Ordered](a, b T) T {
          if a > b {
              return a
          }
          return b
      }
    

在这个例子中,我们使用了 constraints 包中的 Ordered 约束,它包括了所有支持比较操作的基本类型,如 intfloat64stringMax 函数的类型参数 T 受此约束限制,只能接受这些类型。

comparcable类型

约束类型必须支持 == 和 != 操作,包括基本类型(int、string 等)及包含可比较字段的结构体。

示例:

func Contains[T comparcable](slice []T, target T) bool

{ /* 查找元素 */ }

类型联合(Union Types)

使用 | 符号定义多类型集合,例如:

type Numeric interface { int | float64 }

// 允许 int 或 float64 类型

近似约束(Approximation Constraint)

通过 ~ 符号允许底层类型匹配,例如:

type MyInt int

type IntAlias interface { ~int }

复合约束

通过接口组合实现多约束条件,例如要求类型同时支持序列化和比较

type SerializableComparable interface {

    json.Marshaler

    comparable 

}

泛型函数

泛型函数是指使用类型参数的函数。定义泛型函数时,需要在函数名后面添加方括号([])和类型参数列表。调用泛型函数时,通常不需要显式指定类型参数,因为编译器可以自动推断:

func main() {
  intSlice := []int{1, 2, 3, 4, 5}
  PrintSlice[int](intSlice) // 可以显式指定类型参数,但通常不需要
  PrintSlice(intSlice)      // 类型参数自动推断
  stringSlice := []string{"hello", "world"}
  PrintSlice(stringSlice) // 类型参数自动推断
}

泛型类型

泛型类型是指使用类型参数的类型,如结构体、接口等。以下是一个定义泛型结构体的简单示例:

type Pair[T any] struct {
  First, Second T
}
func main() {
  p1 := Pair[int]{First: 1, Second: 2}
  p2 := Pair[string]{First: "hello", Second: "world"}
}

类型推断

类型推断是指编译器根据泛型函数或类型的实际参数自动推断出类型参数的过程。这使得调用泛型函数或使用泛型类型时无需显式指定类型参数,提高了代码的简洁性。

编写约束

可以通过定义接口来创建自定义约束。约束接口可以包含一个或多个类型集,每个类型集指定一组允许的类型。以下是一个自定义约束的示例:

package main
type UnsignedInteger interface {
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
func Add[T UnsignedInteger](a, b T) T {
    return a + b
}

在这个例子中,我们定义了一个名为 UnsignedInteger 的约束接口,它允许无符号整数类型。Add 函数的类型参数 T 受此约束限制,只能接受无符号整数类型。

实战代码

// 泛型栈实现
type Stack[T any] struct {
    elements []T
}
func (s *Stack[T]) Push(v T) {
    s.elements = append(s.elements, v)
}
func (s *Stack[T]) Pop() (T, bool) {
    if len(s.elements) == 0 { return zero(T), false }
    v := s.elements[len(s.elements)-1]
    s.elements = s.elements[:len(s.elements)-1]
    return v, true
}

// 使用示例
var intStack Stack[int]
intStack.Push(42)
val, _ := intStack.Pop()

注意事项

  1. 泛型代码可能与非泛型代码不完全兼容,因此在混合使用时需要特别注意。

  2. 泛型可能会增加编译时间,因为编译器需要为每个具体类型生成代码。

  3. 在使用泛型时,应权衡代码复用与类型安全之间的平衡,避免过度设计。

总之,Go 语言中的泛型提供了一种强大的方式来编写可重用且类型安全的代码,使得开发者可以在多种数据类型上执行通用操作。