泛型(Generics)
本章讲解Go泛型:泛型历史、类型参数与约束、泛型函数与数据结构、适用场景与误区。重点:泛型在Go 1.18引入,用于减少类型断言的运行时开销;理解any约束和自定义约束的用法。
前置知识:第6章 接口 学习目标:掌握泛型语法、理解约束的作用、避免泛型滥用
12.1 泛型的诞生与演变
Section titled “12.1 泛型的诞生与演变”Go泛型历史
Section titled “Go泛型历史”- Go 1.0 - 1.17:无泛型,使用interface和类型断言
- Go 1.18(2022年3月):正式引入泛型
// 使用interface{}存储任意类型type Stack struct { items []interface{}}
func (s *Stack) Push(item interface{}) { s.items = append(s.items, item)}
func (s *Stack) Pop() interface{} { if len(s.items) == 0 { return nil } item := s.items[len(s.items)-1] s.items = s.items[:len(s.items)-1] return item}
// 使用需要类型断言stack := &Stack{}stack.Push(1)stack.Push("hello")item := stack.Pop().(string) // 类型断言,可能panic// 泛型Stacktype 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 { if len(s.items) == 0 { var zero T return zero } item := s.items[len(s.items)-1] s.items = s.items[:len(s.items)-1] return item}
// 使用时指定类型stack := &Stack[int]{}stack.Push(1)// stack.Push("hello") // 编译错误:必须是int12.2 类型参数与约束
Section titled “12.2 类型参数与约束”本节目的:掌握泛型类型参数语法、
any约束和自定义约束
类型参数语法
Section titled “类型参数语法”// 类型参数用方括号func printSlice[T any](s []T) { for _, v := range s { fmt.Print(v, " ") } fmt.Println()}
// 完整语法func comparison[T comparable](a, b T) bool { return a == b}any是空接口的别名,等同于interface{}:
// 这两个等价func f1[T any](x T) {}func f2[T interface{}](x T) {}comparable约束
Section titled “comparable约束”comparable约束表示类型可以比较(可以用==和!=):
// 泛型函数要求类型可比较func contains[T comparable](s []T, target T) bool { for _, v := range s { if v == target { return true } } return false}
func main() { fmt.Println(contains([]int{1, 2, 3}, 2)) // true fmt.Println(contains([]string{"a", "b"}, "c")) // false}// 定义约束type Number interface { int | int32 | int64 | float64 | float32}
func sum[T Number](values []T) T { var total T for _, v := range values { total += v } return total}
func main() { fmt.Println(sum([]int{1, 2, 3})) // 6 fmt.Println(sum([]float64{1.5, 2.5})) // 4.0}联合类型约束
Section titled “联合类型约束”// 允许的类型联合type StringOrInt interface { string | int}
func convert[T StringOrInt](v T) string { return fmt.Sprintf("%v", v)}
func main() { fmt.Println(convert("hello")) // hello fmt.Println(convert(123)) // 123}12.3 泛型函数与泛型数据结构
Section titled “12.3 泛型函数与泛型数据结构”本节目的:掌握泛型函数编写、泛型数据结构的实现
// 交换两个值func swap[T any](a, b T) (T, T) { return b, a}
// 找最大值func max[T comparable](a, b T) T { if a >= b { return a } return b}
// Map函数func mapSlice[T, U any](s []T, f func(T) U) []U { result := make([]U, len(s)) for i, v := range s { result[i] = f(v) } return result}
func main() { numbers := []int{1, 2, 3} strings := mapSlice(numbers, func(n int) string { return fmt.Sprintf("num_%d", n) }) fmt.Println(strings) // [num_1 num_2 num_3]}泛型数据结构
Section titled “泛型数据结构”// 泛型Pairtype Pair[K, V any] struct { Key K Value V}
// 泛型链表type List[T any] struct { value T next *List[T]}
// 泛型Treetype Tree[T any] struct { value T left *Tree[T] right *Tree[T]}type Container[T any] struct { items []T}
// 方法可以继续使用泛型func (c *Container[T]) Get(index int) T { return c.items[index]}
func (c *Container[T]) Size() int { return len(c.items)}12.4 泛型的适用场景与误区
Section titled “12.4 泛型的适用场景与误区”本节目的:理解泛型的适用场景、避免常见误区
1. 数据结构
// 通用数据结构type Stack[T any] struct { items []T}
type Queue[T any] struct { items []T}
type HashMap[K comparable, V any] struct { // ...}2. 算法实现
// 通用排序func sort[T any](s []T, less func(a, b T) bool) []T { // 排序逻辑 copy(s, sortUtil(s, less)) return s}
// 使用data := []int{3, 1, 2}sort(data, func(a, b int) bool { return a < b})3. 工具函数
// 管道式处理func filter[T any](s []T, predicate func(T) bool) []T { result := make([]T, 0) for _, v := range s { if predicate(v) { result = append(result, v) } } return result}误区1:泛型影响运行时性能
实际上,泛型在编译时展开,无运行时开销:
// 泛型func identity[T any](v T) T { return v }
// 编译后类似:func identity_int(v int) int { return v }func identity_string(v string) string { return v }误区2:所有场景都应用泛型
简单场景用具体类型更清晰:
// 过度泛型func process[T any](data []T) []T { ... }
// 更清晰func processInts(data []int) []int { ... }误区3:泛型可以替代interface
泛型用于类型安全的数据结构和算法,interface用于运行时多态。
// 泛型:编译时知道类型type Cache[T any] struct { data map[string]T}
// interface{}:运行时才知道类型type AnyCache struct { data map[string]interface{}}泛型与接口的选择
Section titled “泛型与接口的选择”// 选择泛型:类型参数影响函数签名func first[T any](s []T) T { return s[0]}
// 选择interface:需要多种类型实现同一行为type Stringer interface { String() string}
func printAll[T Stringer](items []T) { for _, item := range items { fmt.Println(item.String()) }}- 泛型用于数据结构:Stack、Queue、LinkedList等数据结构用泛型,避免类型断言
any约束最宽松:[T any]允许任何类型,用于不依赖类型行为的场景 3comparable约束比较:[T comparable]用于需要==/!=操作的类型参数- 不要滥用泛型:如果只有两三个类型需要处理,用具体类型更清晰
- 约束让代码更安全:用自定义约束限制类型参数的能力,如
[T Number]限制为数字类型
| Go 1.17- | Go 1.18+ |
|---|---|
interface{}存储任意类型 | any(interface别名) |
| 类型断言 | 泛型函数 |
| 无 | [T any]类型参数 |
| 无 | comparable约束 |
泛型使用原则:
- 泛型用于数据结构和通用算法
- 约束让类型参数更精确
- 泛型无运行时开销
- 不要过度使用泛型
- 实现泛型Stack数据结构
- 实现泛型Map函数,处理切片并返回新切片
- 定义约束实现整数求和函数
- 实现泛型链表
- 比较interface和泛型的使用场景