本文简单介绍go泛型的概念和使用。
func Add(a int, b int) int {
return a + b
}
这个函数很简单,但是它无法计算int类型之外的和。如果我们想计算浮点或者字符串的和该怎么办?解决方法就是对它进行方法的重载。但是golang不支持对方法进行重载。
//错误
func AddFloat32(a float32, b float32) float32 {
return a + b
}
func AddString(a float64, b float64) float64{
return a + b
}
可以通过泛型来解决。
func Add[T int | float64](a T, b T) T {
return a + b
}
泛型方法:该方法在调用时可以接收不同类型
的参数。一个函数获得了处理多种不同类型数据
的能力,这种编程方式被称为 泛型编程
。
泛型有着自己的适用场景: 如果你经常要分别为不同的类型
写完全相同逻辑的代码,那么使用泛型将是最合适的选择。
之前我们定义一个可以容纳 int 或 float32或 int32 等其他类型的切片。
type intSlice []int
type Float32Slie []float32
type int32Slice []int32
现在我们可以这样
//不同于一般的类型定义,这里类型名称 SliceT 后带了中括号
type SliceT[T int | float32 | int32 ] []T
SliceT
类型的时候 T 代表的具体类型并不确定,类似一个占位符。int | float32 | int32
中间的 | 的意思是告诉编译器,类型形参 T 只可以接收 int 或 float32 或 float64 这三种类型的实参。 T int | float32 | int32
。SliceT[T]
。泛型类型:类型定义中带
类型形参
的类型
type SliceT[T int | float32 | int32 | string] []T
func SliceFunc() {
// 这里传入了类型实参int,泛型类型SliceT[T]被实例化为具体的类型 Slice[int]
var a SliceT[int] = []int{1, 2, 3}
fmt.Printf("Type Name: %T\n", a) //输出:Type Name: Slice[int]
// 传入类型实参float32, 将泛型类型SliceT[T]实例化为具体的类型 Slice[string]
var b SliceT[float32] = []float32{1.0, 2.0, 3.0}
fmt.Printf("Type Name: %T\n", b) //输出:Type Name: Slice[float32]
// ✗ 错误。string不在类型约束 int|float32|float64 中,不能用来实例化泛型类型
//var c SliceT[string] = []string{"Hello", "World"}
}
type MyMap[KEY int | string, VALUE float32 | float64] map[KEY]VALUE
func MapFunc() {
var mp MyMap[string, float64] = map[string]float64{
"jack_score": 9.6,
"bob_score": 8.4,
}
fmt.Println(mp)
}
type MyChan[T int | string] chan T
func ChanFunc() {
ch := make(MyChan[int], 3)
for i := 1; i <= 3; i++ {
ch <- i
}
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
}
// 一个泛型接口
type IPrintData[T int | float32 | string] interface {
Print(data T)
}
// 一个泛型类型的结构体。可用 int 或 sring 类型实例化
type MyStruct[T int | string] struct {
Name string
Data T
}
type MyStruct2[T int | string, A int | bool] struct {
Name string
Data T
Sex A
}
func StructFunc() {
var my MyStruct[int] = MyStruct[int]{
Name: "caicai",
Data: 23,
}
fmt.Println(my)
var my2 MyStruct2[int, bool] = MyStruct2[int, bool]{
Name: "caicai",
Data: 23,
Sex: true,
}
fmt.Println(my2)
}
type SliceT[T int | float32 | int32 | string] []T
type SliceT2[T int | float32 | int32] []T
type MyStruct3[T int | string] struct {
Name string
Data T
habby SliceT[T]
//habby2 SliceT2[T]
}
func Struct2Func() {
var my MyStruct3[string] = MyStruct3[string]{
Name: "caicai",
Data: "23",
habby: []string{"swim"},
}
fmt.Println(my)
}
我们知道 MyStruct3
的约束是 int或 string
当传入的 string
时,属性habby2 不满足string
,报错。
// 错误,类型形参不能单独使用
type CommonType[T int|string|float32] T
//✗ 错误。T *int会被编译器误认为是表达式 T乘以int,而不是int指针
type NewType[T *int] []T
// 上面代码再编译器眼中:它认为你要定义一个存放切片的数组,数组长度由 T 乘以 int 计算得到
type NewType [T * int][]T
//✗ 错误。和上面一样,这里不光*被会认为是乘号,| 还会被认为是按位或操作
type NewType2[T *int|*float64] []T
//✗ 错误
type NewType2 [T (int)] []T
- 为了避免这种误解,解决办法就是给类型约束包上 interface{} 或加上逗号消除歧义
type NewType[T interface{*int}] []T
type NewType2[T interface{*int|*float64}] []T
// 如果类型约束中只有一个类型,可以添加个逗号消除歧义
type NewType3[T *int,] []T
//✗ 错误。如果类型约束不止一个类型,加逗号是不行的
type NewType4[T *int|*float32,] []T
因为上面逗号的用法限制比较大,这里推荐统一用 interface{} 解决问题
// 先定义个泛型类型 Slice[T]
type Slice[T int | string | float32 | float64] []T
// ✗ 错误。泛型类型Slice[T]的类型约束中不包含uint, uint8
//type UintSlice[T uint | uint8] Slice[T]
// ✓ 正确。基于泛型类型Slice[T]定义了新的泛型类型 FloatSlice[T] 。FloatSlice[T]只接受float32和float64两种类型
type FloatSlice[T float32 | float64] Slice[T]
// 在map中套一个泛型类型Slice[T]
type WowMap[T int | string] map[string]Slice[T]