Go语言泛型基础
f559facb-f001-463f-97ab-1a9884f3dada
泛型
Go 并不是一种静止的、一成不变的编程语言。新的功能是在经过大量的讨论和实验后慢慢采用的。最初的 Go1.0发布以来,Go语言习惯的模式已经发生了重大变化1.7的context、1.11的modules、1.13 error嵌套等Go的 1.18 版本包括了类型参数的实现,也就是俗称的泛型泛型虽然很受期待,但实际上推荐的使用场景也并没有那么广泛。但是我们作为学习者,一定要了解学会,遇到了至少不懵逼!
package main
import "fmt"
func main() {
stars := []string{"wk", "ce"}
printArray(stars)
is := []int{1, 2}
printArray(is)
}
// **我们不限定他类型,让调用者自己去定义类型。T ** <>// []T 形式类型实际类型
/*
**内置的泛型类型any和comparable**
**any:** 表示go里面所有的内置基本类型,等价于 interface}
**comparable:** 表示go里面所有内置的可比较类型:int、uint、float、bool、struct、指针'等一切可以比较的类型
*/
func printArray[T string | int](arr []T) {
for _, a := range arr {
fmt.Println(a)
}
}
小结:泛型的作用
- 泛型减少重复代码并提高类型安全性。
- 使用场景:当你需要针对不同类型书写相同逻辑时,使用泛型来简化代码是最好的处理方法。
泛型类型
观察下面这个简单的例子:
type s1 []int
var a sl = []int{1,2,3} // 正确
var b s1 = []float321.0,2.0,3,0} // 错误,因为Intslice的底层类型是[]int,浮点类型的切片无法赋值
这里定义了一个新的类型IntSlice,它的底层类型是 int,理所当然只有int类型的切片能赋值给 ntSlice 类型的变量。
接下来如果我们想要定义一个可以容纳 loat32 或 string 等其他类型的切片的话该怎么办?很简单,给每种类型都定义个新类型
type sl []int
type s2 []float32
type s3 []float643
go但是这样做的问题显而易见,它们结构都是一样的只是成员类型不同就需要重新定义这么多新类型。那么有没有一个办法能只定义一个类型就能代表上面这所有的类型呢?答案是可以的,这时候就需要用到泛型了:
type slice[T int|float32lfloat64] []T
- 不同于一般的类型定义,这里类型名称 Slice 后带了中括号,对各个部分做一个解说就是
- T就是上面介绍过的类型形参(Type parameter),在定义Slice类型的时候T代表的具体类型并不确定,类似一个占位符
- int float32 float64 这部分被称为类型约束(Type constraint),中间的 的意思是告诉编译器,类型形参T只可以接收 int 或 float32或float64这三种类型的实参
- 中括号里的 Tint float32 flat64 这一整串因为定义了所有的类型形参(在这个例子里只有一个类型形参T),所以我们称其为 类型形参列表(type parameter list)
- 这里新定义的类型名称叫 slice[T]
这种类型定义的方式中带了类型形参,很明显和普通的类型定义非常不一样,所以我们将这种类型定义中带 类型形参 的类型,称之为 泛型类型
package main
import "fmt"
func main() {
//定义一个泛型类型切片
type Slice[T int | float64 | float32] []T
var a Slice[int] = []int{1, 2, 3}
fmt.Println(a)
fmt.Printf("%T", a)
var b Slice[float32] = []float32{1, 2, 3}
fmt.Println(b)
fmt.Printf("%T", b)
var c Slice[float64] = []float64{1, 2, 3}
fmt.Println(c)
fmt.Printf("%T", c)
//定义一个泛型类型map
type myMap[KEY int | string, VALUE any] map[KEY]VALUE
var m1 myMap[string, any] = map[string]any{
"go": 9.9,
"java": 9.0,
}
fmt.Println(m1)
}
所有类型定义都可使用类型形参,所以下面这种结构体以及接口的定义也可以使用类型形参:
// 一个泛型类型的结构体。可用 int 或 sring 类型实例化
type MyStruct[T int | string] struct {
Id T 1 "uuid"
Name string
}
// 一个泛型接口
type IPrintData[T int | float32 | string] interface {
Print(data T)
}
// 一个泛型通道,可用类型实参 int 或 string 实例化
type MyChan[T int | string] chan T
特殊的泛型类型
这里讨论种比较特殊的泛型类型,如下
type wow[T int | string] int
var a wow[int] = 1233// 编译正确
var b wow[string] = 123 // 编译正确
var c Wow[string] ="he11o” // 编译错误,因为"he11o"不能赋值给底层类型int
这里虽然使用了类型形参,但因为类型定义是 type wow[Tint|string] int ,所以无论传入什么类型实参,实例化后的新类型的底层类型都是int。所以int类型的数字123可以赋值给变量a和b,但string类型的字符串“hello”不能赋值给c
这个例子没有什么具体意义,但是可以让我们理解泛型类型的实例化的机制
泛型函数
package main
import "fmt"
type MySlice[T int | float64] []T
// Sum 给泛型添加方法
func (s MySlice[T]) Sum() T {
var sum T
for _, v := range s {
sum += v
}
return sum
}
// Add 泛型函数
func Add[T int | float64 | string](a T, b T) T {
return a + b
}
func main() {
var s MySlice[int] = []int{1, 2, 3, 4, 5}
fmt.Println(s.Sum())
var f MySlice[float64] = []float64{1.0, 2.1, 3.2, 4.3, 5.7}
fmt.Println(f.Sum())
fmt.Println(Add(1, 2))
fmt.Println(Add(1.2, 2.3))
fmt.Println(Add("3333", "4444"))
}
自定义泛型类型
package main
import "fmt"
// MyInt 自定义泛型约束
type MyInt interface {
int | int8 | int16 | int32 | int64
}
func GetMaxNum[T MyInt](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(GetMaxNum(10, 20))
}