大家好,在最近遇到关于泛型的问题,网络资料很简陋,本人通过学习后整理出笔记分享出来。
写作背景
争议巨大但同时万众期待的泛型终于在Go 1.18发布,它支持并行处理流中的数据。
本文力求能让未接触泛型编程的人快速上手Go的泛型。
泛型的概念和泛型函数
泛型的含义:在定义函数(结构等)时候,可能会有多种类型传入,真正使用方法的时候才可以确定用的是什么类型,此时就可以用一个更加宽泛的类型(存在一定约束,只能在哪些类型的范围内使用)暂时占位,这个类型就叫泛型。
泛型的基本写法:[泛型标识 泛型约束] [T any]
假设我们有个计算两数之和的函数:
func Add(a int, b int) int {
return a + b
}
这个函数很简单,但是我想计算浮点类型的数或者字符串相加,哪有什么办法呢?解决办法之一是定义不同类型的函数,如下:
func AddFloat32(a float32, b float32) float32 {
return a + b
}
func AddString(a string, b string) string {
return a + b
}
这样的写法虽然能实现想要的效果,但是代码冗余和阅读性大大降低,有什么办法解决这个问题呢?
我们通过使用泛型这个概念能优雅地实现这些功能
func Add[T int | float32 | string](a T, b T) T {
return a + b
}
func main() {
c := Add[string]("1", "2")
d := Add[int](1,2)
fmt.Println(c)
fmt.Println(d)
}
//"12"
//3
声明一个Add函数:
-
Add函数中T是类型形参,在定义Add方法时,T代表的具体类型并不确定,类似一个占位符。
-
int | float32 | string 这部分称为类型约束,中间的|的意思是告诉编译器,类型形参T值可以接受int,float32或string这三种类型。
-
中括号里的 T int|float32|string 这一整串因为定义了所有的类型形参(在这个例子里只有一个类型形参T),所以我们称其为类型形参列表
必须传入类型实参 将其确定为具体的类型之后才可使用。而传入类型实参确定具体类型的操作被称为 实例化 :
func main() {
c := Add[string]("1", "2") //传入类型实参为string,
d := Add[int](1,2) //传入类型实参为int
fmt.Println(c)
fmt.Println(d)
}
//"12"
//3
结构体泛型
type Person[T any] struct {
name string
sex T
class T
}
func main() {
t := Person[int]{name: "cbz", sex: 1, class: 1}
fmt.Println(t)
}
//{cbz 1 1}
结构体泛型多个变量
type Person[T any, S any] struct {
name T
class S
}
func main() {
p := Person[string, int]{name: "cbz", class: 123}
fmt.Println(p)
}
//{cbz 123}
使用,分割,就可以实现多个泛型的实现。
map泛型
type TMap[K comparable, V string | int] map[K]V
func main() {
m := make(TMap[int, string])
m[123] = "123dsf"
fmt.Println(m)
}
//map[123:123dsf]
printf("hello world!");
Slice泛型
type TSlice[S any] []S
func main() {
s := make(TSlice[int], 6)
s[5] = 545
fmt.Println(s)
}
// [0 0 0 0 0 545]
~:指定底层类型
type SliceElement interface {
int | uint | string
}
type Slice[T SliceElement] []T
func main() {
var s1 Slice[int] //正确
type MyInt int
var s2 Slice[MyInt]
//错误。MyInt类型底层类型虽然是int,
//但是不是int类型,不符合Slice[T]的类型约束
}
这里发生错误的原因是,泛型类型Slice[T]允许int作为类型实参,但是不允许以int为底层类型的Myint类型。
为了从根本上解决和这个问题,Go新增了一个符号~,为类型实参增加了广泛性,这样代表着不光是int,以int为底层类型的类型也都可用于实例化。
使用~如下:
type SliceElement interface {
~int | uint | string
}
type Slice[T SliceElement] []T
func main() {
var s1 Slice[int] //正确
type MyInt int
var s2 Slice[MyInt] //正确。MyInt类型虽然是MyInt,但是底层类型是int,允许实例化
}
限制:使用~时限制:
1,~后面的类型不能是接口
2,~后面的类型必须为基本类型
type test1 interface {
~[]byte // 正确
~MyInt // 错误,~后的类型必须为基本类型
~error // 错误,~后的类型不能为接口
}
结构体并集
当定义一个泛型接口时,需要支持多个基本类型一般做法:
type AllInt interface { // 类型集 Uint 是 ~uint 和 ~uint8 等类型的并集
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint32
}
这样的代码阅读性大大降低。若一个接口有多行类型定义,可以取它们之间的交集
type AllInt interface { // 类型集 Uint 是 ~uint 和 ~uint8 等类型的并集
~int | ~int8 | ~int16 | ~int32 | ~int64
}
type AllUint interface{
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint32
}
//接口A代表的类型是AllInt和AllUint的交集,
//也就是说支持所有AllInt和ALLUint所支持的类型。
type A interface{
AllInt
AllUint
}
总结
泛型是Go 1.18中一个很大新语言特性,泛型的出现能使用我们的代码质量很高,更有效率。但也因为新特性,会带来学习成本,但我们很高兴能有泛型可用,我们希望它们能让 Go 程序员更有效率。
参考资料
Go 1.18 泛型全面讲解:一篇讲清泛型的全部
泛型的入门和基础使用
文章评论