go1.18之后引入
核心概念
泛型主要通过 类型参数(Type Parameters) 和 类型约束(Type Constraints) 来实现。
类型参数 (Type Parameters)
类型参数是一个占位符,代表一个未知的类型。在定义函数或类型时,你可以声明一个或多个类型参数。
语法:使用方括号 [] 来声明类型参数,通常放在函数名或类型名之后
如
// T 就是一个类型参数
func Show[T any](s []T){}
类型约束 (Type Constraints)
类型约束定义了类型参数可以接受哪些具体的类型。它是一个接口类型,规定了类型参数必须满足的条件(比如必须支持某些操作或拥有某些方法)。
Go 内置了两个常用的约束:
1.any: 它是 interface{} 的别名,表示任何类型都可以作为类型参数。这是最宽松的约束。
2.comparable: 表示该类型支持使用 == 和 != 进行比较。所有基础类型(如 int, string, bool)都满足这个约束,但像 slice、map 和 func 则不满足。
如何使用泛型
泛型函数 (Generic Functions)
下面是一个简单的泛型函数示例,它可以打印任何类型的切片(slice)。
import "fmt"
// Show 是一个泛型函数
// [T any] 定义了一个名为 T 的类型参数,它的约束是 any(任何类型)
func Show[T any](s []T) {
for _, v := range s {
fmt.Printf("%v ", v)
}
fmt.Println()
}
func main() {
intShow := []int{1, 2, 3}
stringShow := []string{"hello", "world"}
// 调用泛型函数时,通常不需要显式指定类型
// Go 编译器会自动推断出 T 是 int
Show(intShow) // 输出: 1 2 3
// 编译器会自动推断出 T 是 string
Show(stringShow) // 输出: hello world
}
自定义约束
你也可以通过定义一个接口来创建自己的约束。例如,我们想写一个泛型函数来求两个数的和,那么这两个数必须支持 + 操作。
// Number 是一个自定义约束,它约束了类型参数必须是 int 或 float64
type Number interface {
int | float64 // 使用 | 来联合多个类型
}
// Add 是一个泛型函数,它只能接受满足 Number 约束的类型
func Add[T Number](a, b T) T {
return a + b
}
func main() {
fmt.Println(Add(1, 2)) // 输出: 3
fmt.Println(Add(1.5, 2.5)) // 输出: 4.0
// 下面这行会编译错误,因为 string 不满足 Number 约束
// fmt.Println(Add("a", "b"))
}
泛型类型 (Generic Types)
除了函数,你还可以定义泛型的数据结构,比如一个栈(Stack)或链表。
下面是一个可以存放任何类型数据的泛型 Stack 的例子:
// Stack 是一个泛型类型
// [T any] 表示这个 Stack 可以存放任何类型的数据
type Stack[T any] struct {
items []T
}
// Push 方法向栈中添加一个元素
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
// Pop 方法从栈中弹出一个元素
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T // 声明一个 T 类型的零值
return zero, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}func main() {
// 创建一个存放 int 的栈
intStack := &Stack[int]{}
intStack.Push(10)
intStack.Push(20)
val, _ := intStack.Pop()
fmt.Println(val) // 输出: 20
// 创建一个存放 string 的栈
stringStack := &Stack[string]{}
stringStack.Push("A")
stringStack.Push("B")
strVal, _ := stringStack.Pop()
fmt.Println(strVal) // 输出: B
}Go 泛型的优势:
1.代码复用:为多种类型编写通用的、一次性的代码。
2.类型安全:在编译时就能捕获类型错误,而不是在运行时。
3.代码更清晰:不再需要 interface{} 和类型断言,逻辑更直观。