大家好,在最近遇到关于泛型的问题,网络资料很简陋,本人通过学习后整理出笔记分享出来。
写作背景
争议巨大但同时万众期待的泛型终于在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) //传入类型实参为intfmt.Println(c)fmt.Println(d)}//"12"//3
结构体泛型
type Person[T any] struct {name stringsex Tclass 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 Tclass 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]Vfunc main() {m := make(TMap[int, string])m[123] = "123dsf"fmt.Println(m)}//map[123:123dsf]printf("hello world!");
Slice泛型
type TSlice[S any] []Sfunc main() {s := make(TSlice[int], 6)s[5] = 545fmt.Println(s)}// [0 0 0 0 0 545]
~:指定底层类型
type SliceElement interface {int | uint | string}type Slice[T SliceElement] []Tfunc main() {var s1 Slice[int] //正确type MyInt intvar 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] []Tfunc main() {var s1 Slice[int] //正确type MyInt intvar 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{AllIntAllUint}
总结
泛型是Go 1.18中一个很大新语言特性,泛型的出现能使用我们的代码质量很高,更有效率。但也因为新特性,会带来学习成本,但我们很高兴能有泛型可用,我们希望它们能让 Go 程序员更有效率。
参考资料
Go 1.18 泛型全面讲解:一篇讲清泛型的全部
泛型的入门和基础使用
文章评论