Go 语言泛型 - GO教程

Go 语言泛型

泛型是 Go 语言在 1.18 版本中引入的重要特性,它让开发者能够编写更加灵活和可重用的代码。

泛型主要通过以下两个核心概念来实现:

  • 类型参数(Type Parameters): 允许你在函数或类型定义中使用一个或多个类型作为参数。
  • 类型约束(Type Constraints): 指定类型参数必须满足的条件,确保在函数内部可以安全地操作这些类型。
概念 作用 示例
类型参数 在函数或类型名后声明,表示待定的类型。 [T any]
类型约束 定义类型参数必须满足的条件(如支持的操作符或方法)。 int,float64,comparable,constraints.Ordered,any
any 约束类型参数为 任何 类型。 [T any]
comparable 约束类型参数为 可比较 的类型。 [K comparable]

泛型(Generics)允许我们编写不依赖特定数据类型的代码。

在引入泛型之前,如果我们想要处理不同类型的数据,通常需要为每种类型编写重复的函数。

传统方式的局限性:

示例代码
// 处理 int 类型的函数funcMaxInt(a,bint)int{ifa > b{returna}returnb}// 处理 float64 类型的函数funcMaxFloat(a,bfloat64)float64{ifa > b{returna}returnb}

使用泛型的解决方案:

示例代码
// 一个函数处理多种类型funcMax[T comparable](a,b T)T{ifa > b{returna}returnb}

泛型语法详解

类型参数声明

泛型函数和类型通过类型参数列表来声明,语法为 [类型参数 约束]

示例代码
// 基本语法结构func函数名[T 约束](参数 T)返回值类型{// 函数体}type类型名[T 约束]struct{// 结构体字段}

类型参数命名约定

  • 通常使用大写字母: T K V E
  • T :表示 Type(类型)
  • K :表示 Key(键)
  • V :表示 Value(值)
  • E :表示 Element(元素)

约束(Constraints)

约束定义了类型参数必须满足的条件,是泛型的核心概念。

内置约束

1. any 约束

any 是空接口 interface{} 的别名,表示任何类型都可以。

示例代码
funcPrintAny[T any](value T){fmt.Printf("Value: %v, Type: %T\n",value,value)}// 使用示例PrintAny(42)// Value: 42, Type: intPrintAny("hello")// Value: hello, Type: stringPrintAny(3.14)// Value: 3.14, Type: float64

2. comparable 约束

comparable 表示类型支持 == != 操作符。

示例代码
funcFindIndex[T comparable](slice[]T,target T)int{fori,v:=rangeslice{ifv==target{returni}}return-1}// 使用示例numbers:=[]int{1,2,3,4,5}fmt.Println(FindIndex(numbers,3))// 输出: 2names:=[]string{"Alice","Bob","Charlie"}fmt.Println(FindIndex(names,"Bob"))// 输出: 1

3. 联合约束(Union Constraints)

使用 | 运算符组合多个类型。

示例代码
// 数字类型约束typeNumberinterface{int|int8|int16|int32|int64|uint|uint8|uint16|uint32|uint64|float32|float64}funcAdd[T Number](a,b T)T{returna+b}// 使用示例fmt.Println(Add(10,20))// 输出: 30fmt.Println(Add(3.14,2.71))// 输出: 5.85

自定义约束

1. 方法约束

定义需要特定方法的约束。

示例代码
// 定义 Stringer 约束typeStringerinterface{String()string}funcPrintString[T Stringer](value T){fmt.Println(value.String())}// 实现自定义类型typePersonstruct{NamestringAgeint}func(p Person)String()string{returnfmt.Sprintf("%s (%d years old)",p.Name,p.Age)}// 使用示例person:=Person{Name:"Alice",Age:25}PrintString(person)// 输出: Alice (25 years old)

2. 复杂约束

结合类型和方法要求。

示例代码
// 要求类型是数字且实现 String() 方法typeNumericStringerinterface{NumberString()string}

泛型函数实践

1. 通用工具函数

示例代码
// 交换两个值funcSwap[T any](a,b T)(T,T){returnb,a}// 判断切片是否包含元素funcContains[T comparable](slice[]T,target T)bool{for_,item:=rangeslice{ifitem==target{returntrue}}returnfalse}// 去重函数funcUnique[T comparable](slice[]T)[]T{seen:=make(map[T]bool)result:=[]T{}for_,item:=rangeslice{if!seen[item]{seen[item]=trueresult=append(result,item)}}returnresult}// 使用示例funcmain(){// Swap 示例a,b:=10,20a,b=Swap(a,b)fmt.Printf("a=%d, b=%d\n",a,b)// 输出: a=20, b=10// Contains 示例numbers:=[]int{1,2,3,4,5}fmt.Println(Contains(numbers,3))// 输出: true// Unique 示例duplicates:=[]int{1,2,2,3,4,4,5}unique:=Unique(duplicates)fmt.Println(unique)// 输出: [1 2 3 4 5]}

2. 数学运算函数

示例代码
// 求切片最大值funcMax[T Number](slice[]T)T{iflen(slice)==0{varzero Treturnzero}max:=slice[0]for_,value:=rangeslice[1:]{ifvalue > max{max=value}}returnmax}// 求切片最小值funcMin[T Number](slice[]T)T{iflen(slice)==0{varzero Treturnzero}min:=slice[0]for_,value:=rangeslice[1:]{ifvalue < min{min=value}}returnmin}// 求切片平均值funcAverage[T Number](slice[]T)float64{iflen(slice)==0{return0}varsum Tfor_,value:=rangeslice{sum+=value}returnfloat64(sum)/float64(len(slice))}// 使用示例funcmain(){ints:=[]int{1,5,3,9,2}floats:=[]float64{1.1,5.5,3.3,9.9,2.2}fmt.Printf("Max int: %d\n",Max(ints))// 输出: 9fmt.Printf("Min float: %.1f\n",Min(floats))// 输出: 1.1fmt.Printf("Average: %.2f\n",Average(floats))// 输出: 4.40}

泛型类型

1. 泛型结构体

示例代码
// 泛型栈实现typeStack[T any]struct{elements[]T}// 入栈func(s*Stack[T])Push(value T){s.elements=append(s.elements,value)}// 出栈func(s*Stack[T])Pop()(T,bool){iflen(s.elements)==0{varzero Treturnzero,false}lastIndex:=len(s.elements)-1value:=s.elements[lastIndex]s.elements=s.elements[:lastIndex]returnvalue,true}// 查看栈顶元素func(s*Stack[T])Peek()(T,bool){iflen(s.elements)==0{varzero Treturnzero,false}returns.elements[len(s.elements)-1],true}// 判断栈是否为空func(s*Stack[T])IsEmpty()bool{returnlen(s.elements)==0}// 使用示例funcmain(){// 整数栈intStack:=Stack[int]{}intStack.Push(1)intStack.Push(2)intStack.Push(3)fmt.Println(intStack.Pop())// 输出: 3 true// 字符串栈stringStack:=Stack[string]{}stringStack.Push("hello")stringStack.Push("world")fmt.Println(stringStack.Pop())// 输出: world true}

2. 泛型映射(Map)

示例代码
// 线程安全的泛型映射typeSafeMap[K comparable,V any]struct{datamap[K]Vmutexsync.RWMutex}// 创建新的 SafeMapfuncNewSafeMap[K comparable,V any]()*SafeMap[K,V]{return&SafeMap[K,V]{data:make(map[K]V),}}// 设置键值对func(m*SafeMap[K,V])Set(key K,value V){m.mutex.Lock()deferm.mutex.Unlock()m.data[key]=value}// 获取值func(m*SafeMap[K,V])Get(key K)(V,bool){m.mutex.RLock()deferm.mutex.RUnlock()value,exists:=m.data[key]returnvalue,exists}// 删除键func(m*SafeMap[K,V])Delete(key K){m.mutex.Lock()deferm.mutex.Unlock()delete(m.data,key)}// 获取所有键func(m*SafeMap[K,V])Keys()[]K{m.mutex.RLock()deferm.mutex.RUnlock()keys:=make([]K,0,len(m.data))forkey:=rangem.data{keys=append(keys,key)}returnkeys}// 使用示例funcmain(){// 创建字符串到整数的映射scores:=NewSafeMap[string,int]()scores.Set("Alice",95)scores.Set("Bob",87)ifscore,exists:=scores.Get("Alice");exists{fmt.Printf("Alice's score: %d\n",score)// 输出: Alice's score: 95}fmt.Println("Keys:",scores.Keys())// 输出: Keys: [Alice Bob]}

类型推断

Go 编译器能够自动推断类型参数,让代码更加简洁。

示例代码
// 无需显式指定类型funcmain(){// 类型推断示例fmt.Println(Max([]int{1,2,3}))// 编译器推断 T 为 intfmt.Println(Max([]float64{1.1,2.2}))// 编译器推断 T 为 float64// 显式指定类型(有时需要)varresultint=Max[int]([]int{1,2,3})fmt.Println(result)}

实践练习

练习 1:实现泛型过滤器

编写一个 Filter 函数,根据条件过滤切片元素。

示例代码
// 你的实现代码在这里funcFilter[T any](slice[]T,predicatefunc(T)bool)[]T{// 实现过滤逻辑}// 测试代码funcmain(){numbers:=[]int{1,2,3,4,5,6}even:=Filter(numbers,func(nint)bool{returnn%2==0})fmt.Println(even)// 应该输出: [2 4 6]}

练习 2:实现泛型映射函数

编写一个 Map 函数,将切片中的每个元素转换为另一种类型。

示例代码
// 你的实现代码在这里funcMap[T any,U any](slice[]T,mapperfunc(T)U)[]U{// 实现映射逻辑}// 测试代码funcmain(){numbers:=[]int{1,2,3,4,5}strings:=Map(numbers,func(nint)string{returnfmt.Sprintf("Number: %d",n)})fmt.Println(strings)}

常见问题与注意事项

1. 性能考虑

泛型在编译时进行类型特化,运行时性能与手写特定类型代码相当。

2. 类型约束的选择

  • 使用 any 时最灵活,但功能受限
  • 使用 comparable 支持相等比较
  • 使用联合约束限制可用的具体类型

3. 错误处理

示例代码
// 良好的错误处理实践funcSafeMax[T Number](slice[]T)(T,error){iflen(slice)==0{varzero Treturnzero,errors.New("slice is empty")}returnMax(slice),nil}