讲解
泛型(Go 1.18 引入)让函数和类型可以「参数化类型」:写一次逻辑,对多种类型生效,同时保住编译期类型检查。语法是在函数名后加方括号声明类型参数:func Max[T int | float64](a, b T) T,T 是类型参数,竖线列出允许的类型集合(约束),调用时编译器自动推断 T 的具体类型。
约束(constraint)决定类型参数能做什么:[T any] 完全不限制,但函数体内也只能做所有类型都支持的操作(赋值、比较存进容器);[T constraints.Ordered] 或手写的 int | float64 这类联合约束允许比较运算;还可以用接口做约束。经验是「约束写到刚好够用」——约束越宽函数体内能用的操作越少,越窄可复用的类型越少。
泛型也能定义类型:type Stack[T any] struct { items []T },方法声明里要带上类型参数 func (s *Stack[T]) Push(v T)。标准库的 slices、maps 包(前面见过 slices.Sort)就是泛型的活广告。但要提醒:泛型不是银弹,能用接口、代码生成或干脆写两遍小函数解决的,不要为泛型而泛型——Go 团队的官方建议也是「先写具体代码,重复真的痛了再抽象」。
示例
泛型函数:Max 与 Map:
package main
import "fmt"
func Max[T int | float64](a, b T) T {
if a > b {
return a
}
return b
}
func Map[T any, R any](s []T, f func(T) R) []R {
out := make([]R, len(s))
for i, v := range s {
out[i] = f(v)
}
return out
}
func main() {
fmt.Println(Max(3, 7)) // T 推断为 int
fmt.Println(Max(2.5, 1.8)) // T 推断为 float64
squares := Map([]int{1, 2, 3}, func(n int) int { return n * n })
fmt.Println(squares)
lengths := Map([]string{"go", "generics"}, func(s string) int { return len(s) })
fmt.Println(lengths)
}
泛型类型:一个通用的栈:
package main
import "fmt"
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T // 泛型的零值写法
return zero, false
}
top := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return top, true
}
func main() {
s := Stack[int]{}
s.Push(1)
s.Push(2)
if v, ok := s.Pop(); ok {
fmt.Println("弹出:", v)
}
fmt.Println("剩余:", s.items)
names := Stack[string]{}
names.Push("小明")
fmt.Println(names.Pop())
}
常见坑
- 为炫技而泛型:把只被一种类型调用的函数改成泛型,复杂度上升收益为零。官方博客的建议值得背下来:先写具体类型版本,出现真实的第三种用法再泛化。
- 以为 any 约束里能做比较:T any 的值不能用 ==(除非额外约束 comparable),不能用 >(需要 Ordered)。编译报错时回头检查约束是不是给得太宽。
- 泛型里返回零值卡壳:return nil 对 T 不适用,正确写法是 var zero T; return zero——切片章节的「零值思维」在泛型里依然有效。
- 方法上想加新类型参数:Go 不允许在方法上声明额外的类型参数(只能沿用接收者类型的参数)。func (s Stack[T]) MapR any 是语法错误,这种需求要写成独立的泛型函数。
- 泛型与接口混用无度:泛型解决「同样的逻辑多种类型」,接口解决「不同的行为统一调用」,很多场景两者都行。判断标准:逻辑依赖类型的具体结构用泛型,依赖行为用接口。
小结
类型参数写在方括号里,约束限定可用操作,泛型类型在方法签名里带参数;先具体后抽象。下一节是收官章:Go 最佳实践清单。