泛型基础
这页记录 Go 泛型的基础:类型参数、类型约束、泛型函数、泛型方法和泛型类型,各自怎么定义、怎么用。
2.1 类型参数与类型约束
类型参数是泛型的核心概念,它允许我们在定义函数、方法或类型时使用占位符来表示某种类型,而在实际使用时再指定具体的类型。类型约束则用于限制类型参数可以接受的具体类型范围。
类型参数: 类型参数用方括号[]括起来,置于函数名、方法名或类型名之后。在Go语言中,类型参数通常用单个大写字母表示,如T、K、V等。
类型约束: 类型约束是用来限制类型参数的类型范围的条件。它可以是任何接口类型。Go语言提供了一些预定义的类型约束,如any表示任意类型,comparable表示支持比较操作的类型。
示例:
func PrintSlice[T any](s []T) {
for _, v := range s {
fmt.Println(v)
}
}在这个例子中,T是一个类型参数,any是类型约束,表示T可以是任何类型。
2.2 泛型函数与方法
泛型函数和方法是指使用类型参数定义的函数和方法。它们可以接受和返回不同类型的参数,而无需为每种类型单独定义函数或方法。
泛型函数: 泛型函数在函数名之后使用类型参数,函数体内可以使用这些类型参数来定义参数和返回值。
示例:
func Swap[T any](a, b T) (T, T) {
return b, a
}Swap接受两个相同类型的参数,返回交换后的结果。
泛型方法: 泛型方法与泛型函数类似,但它是定义在泛型类型上的方法,可以使用泛型类型的类型参数。
示例:
type Pair[T any] struct {
first, second T
}
func (p *Pair[T]) Swap() {
p.first, p.second = p.second, p.first
}这个Swap是Pair上的方法,交换它的两个字段。
2.3 泛型类型
泛型类型是指使用类型参数定义的结构体、接口或其他类型。泛型类型可以容纳不同类型的数据,而无需为每种数据类型单独定义类型。
泛型结构体: 泛型结构体在结构体名之后使用类型参数,结构体字段可以使用这些类型参数来定义其类型。
示例:
type Box[T any] struct {
content T
}
func (b *Box[T]) SetContent(content T) {
b.content = content
}
func (b *Box[T]) GetContent() T {
return b.content
}Box能装任何类型的内容,SetContent和GetContent负责读写。
泛型接口: 泛型接口允许我们定义一组操作,可以在不同类型上实现,而无需为每种类型单独定义接口。
示例:
type Container[T any] interface {
Add(item T)
Remove() T
}Container要求实现方提供Add和Remove两个方法。
2.4 类型推断
Go的编译器大多能自动推断类型参数,函数调用、方法调用、类型实例化这几处都用得上,省掉手动写类型。
示例:
func PrintSlice[T any](s []T) {
for _, v := range s {
fmt.Println(v)
}
}
func main() {
intSlice := []int{1, 2, 3}
PrintSlice(intSlice) // 编译器自动推断T为int
}调用处没写类型,编译器从intSlice推出T是int。
2.5 泛型的局限与注意事项
泛型不是没有代价,用的时候有三条要注意:
- 类型参数过多:过多的类型参数会使代码难以理解,应尽量简化。
- 性能开销:泛型代码在某些情况下可能引入性能开销,应注意性能分析和优化。
- 接口约束:类型参数的约束应尽量明确,避免不必要的约束和复杂性。
2.6 小结
这里的要点可以收成一句话:类型参数用方括号声明、约束限定它能取的范围,编译器再推断出具体类型。函数、方法、结构体、接口都能这样参数化,同一件事不必为每个类型各写一遍。我整理了三条局限在 2.5,写泛型时会先核对。
参考资料
- Go 官方文档 · Getting started with generics(外部链接)
- 官方博客 · An Introduction To Generics
- 官方博客 · When To Use Generics
- 语言规范 · 类型参数声明