1.9.1 泛型基础概念 #
泛型(Generics)是一种编程语言特性,允许在定义函数、类型或方法时使用类型参数,从而编写出可以处理多种类型的通用代码。Go 语言在 1.18 版本中正式引入了泛型支持,这是 Go 语言发展史上的一个重要里程碑。
什么是泛型 #
在没有泛型之前,如果我们想要编写一个可以处理不同类型的函数,通常需要使用接口或者为每种类型编写单独的函数。这种方式存在以下问题:
- 代码重复:需要为每种类型编写相似的代码
- 类型安全性差:使用
interface{}
会失去编译时类型检查 - 性能损失:接口调用和类型断言会带来运行时开销
传统方式的局限性 #
让我们看一个简单的例子,实现一个查找切片中最大值的函数:
// 针对 int 类型
func MaxInt(slice []int) int {
if len(slice) == 0 {
return 0
}
max := slice[0]
for _, v := range slice[1:] {
if v > max {
max = v
}
}
return max
}
// 针对 float64 类型
func MaxFloat64(slice []float64) float64 {
if len(slice) == 0 {
return 0
}
max := slice[0]
for _, v := range slice[1:] {
if v > max {
max = v
}
}
return max
}
// 针对 string 类型
func MaxString(slice []string) string {
if len(slice) == 0 {
return ""
}
max := slice[0]
for _, v := range slice[1:] {
if v > max {
max = v
}
}
return max
}
可以看到,这些函数的逻辑完全相同,只是处理的类型不同。这就是泛型要解决的问题。
泛型的解决方案 #
使用泛型,我们可以编写一个通用的函数来处理所有可比较的类型:
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
// 泛型函数:查找切片中的最大值
func Max[T constraints.Ordered](slice []T) T {
if len(slice) == 0 {
var zero T
return zero
}
max := slice[0]
for _, v := range slice[1:] {
if v > max {
max = v
}
}
return max
}
func main() {
// 使用相同的函数处理不同类型
intSlice := []int{3, 1, 4, 1, 5, 9}
fmt.Println("Max int:", Max(intSlice)) // 输出: Max int: 9
floatSlice := []float64{3.14, 2.71, 1.41, 1.73}
fmt.Println("Max float64:", Max(floatSlice)) // 输出: Max float64: 3.14
stringSlice := []string{"apple", "banana", "cherry"}
fmt.Println("Max string:", Max(stringSlice)) // 输出: Max string: cherry
}
泛型语法基础 #
类型参数声明 #
泛型的核心是类型参数(Type Parameters),它们在方括号 []
中声明:
// 基本语法
func FunctionName[T TypeConstraint](param T) T {
// 函数体
}
// 多个类型参数
func FunctionName[T, U TypeConstraint](param1 T, param2 U) (T, U) {
// 函数体
}
类型约束 #
类型约束(Type Constraints)定义了类型参数必须满足的条件。最基本的约束是 any
(等同于 interface{}
):
// 最宽松的约束
func Print[T any](value T) {
fmt.Println(value)
}
// 使用示例
func main() {
Print(42) // int
Print("hello") // string
Print(3.14) // float64
Print(true) // bool
}
类型推断 #
Go 编译器可以根据函数参数自动推断类型参数,这使得泛型函数的调用更加简洁:
func Swap[T any](a, b T) (T, T) {
return b, a
}
func main() {
// 显式指定类型参数
x, y := Swap[int](1, 2)
fmt.Println(x, y) // 输出: 2 1
// 类型推断(推荐方式)
a, b := Swap("hello", "world")
fmt.Println(a, b) // 输出: world hello
}
泛型的优势 #
1. 代码复用 #
泛型允许我们编写一次代码,处理多种类型:
// 泛型栈实现
type 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, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
index := len(s.items) - 1
item := s.items[index]
s.items = s.items[:index]
return item, true
}
func (s *Stack[T]) IsEmpty() bool {
return len(s.items) == 0
}
func main() {
// 整数栈
intStack := &Stack[int]{}
intStack.Push(1)
intStack.Push(2)
intStack.Push(3)
for !intStack.IsEmpty() {
if value, ok := intStack.Pop(); ok {
fmt.Println("Int:", value)
}
}
// 字符串栈
stringStack := &Stack[string]{}
stringStack.Push("first")
stringStack.Push("second")
stringStack.Push("third")
for !stringStack.IsEmpty() {
if value, ok := stringStack.Pop(); ok {
fmt.Println("String:", value)
}
}
}
2. 类型安全 #
泛型在编译时进行类型检查,避免了运行时类型错误:
// 类型安全的映射函数
func Map[T, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
func main() {
numbers := []int{1, 2, 3, 4, 5}
// 将整数转换为字符串
strings := Map(numbers, func(n int) string {
return fmt.Sprintf("num_%d", n)
})
fmt.Println(strings) // 输出: [num_1 num_2 num_3 num_4 num_5]
// 将整数平方
squares := Map(numbers, func(n int) int {
return n * n
})
fmt.Println(squares) // 输出: [1 4 9 16 25]
}
3. 性能优化 #
泛型避免了接口调用和类型断言的开销:
// 使用接口的版本(性能较低)
func SumInterface(slice []interface{}) interface{} {
var sum interface{}
for _, v := range slice {
// 需要类型断言,有运行时开销
switch val := v.(type) {
case int:
if sum == nil {
sum = 0
}
sum = sum.(int) + val
case float64:
if sum == nil {
sum = 0.0
}
sum = sum.(float64) + val
}
}
return sum
}
// 使用泛型的版本(性能更高)
func Sum[T constraints.Ordered](slice []T) T {
var sum T
for _, v := range slice {
sum += v // 直接操作,无运行时开销
}
return sum
}
func main() {
intSlice := []int{1, 2, 3, 4, 5}
fmt.Println("Sum:", Sum(intSlice)) // 输出: Sum: 15
floatSlice := []float64{1.1, 2.2, 3.3}
fmt.Println("Sum:", Sum(floatSlice)) // 输出: Sum: 6.6
}
泛型的适用场景 #
1. 数据结构 #
泛型特别适合实现通用的数据结构:
// 泛型链表节点
type Node[T any] struct {
Value T
Next *Node[T]
}
// 泛型链表
type LinkedList[T any] struct {
head *Node[T]
size int
}
func (ll *LinkedList[T]) Add(value T) {
newNode := &Node[T]{Value: value, Next: ll.head}
ll.head = newNode
ll.size++
}
func (ll *LinkedList[T]) Size() int {
return ll.size
}
func (ll *LinkedList[T]) ToSlice() []T {
result := make([]T, 0, ll.size)
current := ll.head
for current != nil {
result = append(result, current.Value)
current = current.Next
}
return result
}
2. 算法实现 #
泛型使得算法实现更加通用:
// 泛型排序函数
func BubbleSort[T constraints.Ordered](slice []T) {
n := len(slice)
for i := 0; i < n-1; i++ {
for j := 0; j < n-i-1; j++ {
if slice[j] > slice[j+1] {
slice[j], slice[j+1] = slice[j+1], slice[j]
}
}
}
}
// 泛型二分查找
func BinarySearch[T constraints.Ordered](slice []T, target T) int {
left, right := 0, len(slice)-1
for left <= right {
mid := left + (right-left)/2
if slice[mid] == target {
return mid
} else if slice[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
return -1
}
func main() {
numbers := []int{64, 34, 25, 12, 22, 11, 90}
fmt.Println("Original:", numbers)
BubbleSort(numbers)
fmt.Println("Sorted:", numbers)
index := BinarySearch(numbers, 25)
fmt.Printf("Index of 25: %d\n", index)
}
3. 函数式编程 #
泛型为函数式编程提供了强大支持:
// 泛型过滤函数
func Filter[T any](slice []T, predicate func(T) bool) []T {
var result []T
for _, v := range slice {
if predicate(v) {
result = append(result, v)
}
}
return result
}
// 泛型归约函数
func Reduce[T, U any](slice []T, initial U, reducer func(U, T) U) U {
result := initial
for _, v := range slice {
result = reducer(result, v)
}
return result
}
func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 过滤偶数
evens := Filter(numbers, func(n int) bool {
return n%2 == 0
})
fmt.Println("Even numbers:", evens) // 输出: [2 4 6 8 10]
// 计算总和
sum := Reduce(numbers, 0, func(acc, n int) int {
return acc + n
})
fmt.Println("Sum:", sum) // 输出: Sum: 55
// 字符串连接
words := []string{"Hello", "World", "Go", "Generics"}
sentence := Reduce(words, "", func(acc, word string) string {
if acc == "" {
return word
}
return acc + " " + word
})
fmt.Println("Sentence:", sentence) // 输出: Sentence: Hello World Go Generics
}
注意事项与最佳实践 #
1. 避免过度使用 #
不是所有情况都需要使用泛型。对于简单的场景,传统的接口可能更合适:
// 简单场景,使用接口更合适
func PrintValue(v interface{}) {
fmt.Println(v)
}
// 复杂场景,泛型更合适
func ProcessSlice[T any](slice []T, processor func(T) T) []T {
result := make([]T, len(slice))
for i, v := range slice {
result[i] = processor(v)
}
return result
}
2. 合理命名类型参数 #
使用有意义的类型参数名称:
// 不好的命名
func Convert[T, U any](input T) U { /* ... */ }
// 好的命名
func Convert[Input, Output any](input Input) Output { /* ... */ }
// 或者使用约定的单字母名称
func Map[T, R any](slice []T, mapper func(T) R) []R { /* ... */ }
3. 适当使用类型约束 #
根据实际需要选择合适的类型约束:
import "golang.org/x/exp/constraints"
// 需要比较操作时使用 Ordered
func Min[T constraints.Ordered](a, b T) T {
if a < b {
return a
}
return b
}
// 需要数值运算时使用具体的数值约束
func Average[T constraints.Integer | constraints.Float](numbers []T) float64 {
if len(numbers) == 0 {
return 0
}
var sum T
for _, n := range numbers {
sum += n
}
return float64(sum) / float64(len(numbers))
}
通过本节的学习,您应该已经掌握了 Go 泛型的基础概念、语法和基本应用。泛型是一个强大的特性,但需要在合适的场景下使用,以达到代码复用、类型安全和性能优化的目标。在下一节中,我们将深入学习泛型函数和类型的具体实现。