17 泛型
2025/10/3大约 2 分钟
17 泛型
Go 从 1.18 版本开始正式支持泛型,在这之前想要写通用代码只能使用接口配合反射或类型断言,既不安全也不高效。
泛型的核心思想很简单,让类型成为参数,在 cpp 中我们用 template 来实现,而在 Go 中使用类似于 自定义名称[T any]
这样的语法。
基础语法
先来看一下对于函数的泛型,最简单的例子,写一个返回两个数中较大值的函数:
func Max[T int | float64](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(Max(10, 20)) // 20
fmt.Println(Max(3.14, 2.71)) // 3.14
}
结构体、接口等也可以使用泛型,这两个是一样的,看一下结构体就好:
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}
func main() {
intStack := Stack[int]{}
intStack.Push(1)
intStack.Push(2)
val, _ := intStack.Pop()
fmt.Println(val) // 2
strStack := Stack[string]{}
strStack.Push("hello")
strStack.Push("world")
}
当然,也可以同时声明多个模板参数:
type Pair[K comparable, V any] struct {
Key K
Value V
}
func NewPair[K comparable, V any](key K, value V) Pair[K, V] {
return Pair[K, V]{Key: key, Value: value}
}
func main() {
p1 := NewPair("name", "张三")
p2 := NewPair(1, 100)
fmt.Println(p1) // {name 张三}
fmt.Println(p2) // {1 100}
}
类型约束
Go 提供了一些内置的类型约束,比如 any 表示任意类型,comparable 表示可以比较的类型:
func Contains[T comparable](slice []T, target T) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}
当然,也可以自定义约束,此时需要通过接口实现,可以实现对类型、方法的要求,此时和接口的使用是一样的:
type Number interface {
int | int64 | float64
}
func Sum[T Number](nums []T) T {
var sum T
for _, n := range nums {
sum += n
}
return sum
}
类型推导
大多数情况下 Go 可以自动推导类型参数,不需要显式指定,也可以在调用的名字后面直接写上类型:
func Min[T int | float64](a, b T) T {
if a < b {
return a
}
return b
}
func main() {
result1 := Min[int](10, 20) // 显式指定
result2 := Min(10, 20) // 自动推导
}
开发中需要遵守 优先接口,其次泛型,最后反射 的原则,最大程度降低维护成本。