Go 语言在 1.18 版本中引入了泛型(Generics),这是 Go 语言自发布以来最重要的特性之一。泛型允许开发者编写更加通用和灵活的代码,减少重复代码的编写。
泛型的基本概念
泛型允许你编写可以处理多种类型的函数、结构体、接口等,而不需要为每种类型都编写特定的代码。通过使用类型参数(Type Parameters),你可以定义一个通用的函数或结构体,然后在调用时指定具体的类型。
泛型的语法
Go 泛型的核心语法是使用 type
关键字定义类型参数,并在函数或结构体中使用这些类型参数。
1. 泛型函数
package main
import "fmt"
// 定义一个泛型函数,T 是类型参数
func PrintSlice[T any](s []T) {
for _, v := range s {
fmt.Println(v)
}
}
func main() {
intSlice := []int{1, 2, 3, 4, 5}
stringSlice := []string{"a", "b", "c"}
PrintSlice(intSlice) // 输出: 1 2 3 4 5
PrintSlice(stringSlice) // 输出: a b c
}
在这个例子中,PrintSlice
函数可以接受任何类型的切片,并打印其中的元素。T
是类型参数,any
是类型约束,表示 T
可以是任何类型。
2. 泛型结构体
package main
import "fmt"
// 定义一个泛型结构体
type Box[T any] struct {
Content T
}
func main() {
intBox := Box[int]{Content: 42}
stringBox := Box[string]{Content: "Hello"}
fmt.Println(intBox.Content) // 输出: 42
fmt.Println(stringBox.Content) // 输出: Hello
}
在这个例子中,Box
结构体可以存储任何类型的值。T
是类型参数,any
是类型约束。
3. 类型约束
你可以通过接口来定义类型约束,限制类型参数可以接受的类型。
package main
import "fmt"
// 定义一个接口作为类型约束
type Number interface {
int | float64
}
// 定义一个泛型函数,T 必须是 Number 类型
func Sum[T Number](a, b T) T {
return a + b
}
// 定义一个泛型函数,T 必须是 Number 类型
func Sum2[T int | float64](a, b T) T {
return a + b
}
func main() {
var a int = 100
// 调用泛型函数,确保两个参数的类型要相同
fmt.Println(Sum(a, 2)) // 输出: 102
fmt.Println(Sum(1.5, 2.5)) // 输出: 4.0
fmt.Println(Sum2(a, 2)) // 输出: 102
}
在这个例子中,Sum
函数只能接受 int
或 float64
类型的参数,因为 Number
接口限制了类型参数 T
的范围。
注意:
在Go语言中,泛型函数要求传入的参数类型必须完全一致,否则可能会编译报错!
泛型的优点
-
代码复用:泛型允许你编写通用的代码,减少重复代码的编写。
-
类型安全:泛型在编译时进行类型检查,避免了运行时类型错误。
-
性能:泛型代码在编译时会生成特定类型的代码,避免了运行时类型转换的开销。
泛型的局限性
-
复杂性:泛型增加了语言的复杂性,可能会使代码更难理解。
-
编译时间:泛型代码可能会增加编译时间,因为编译器需要为每种类型生成特定的代码。
-
工具支持:由于泛型是相对较新的特性,一些工具(如 IDE、静态分析工具)可能还没有完全支持