Go泛型快速入门

2022年7月3日 270点热度 0人点赞 0条评论

    大家好,在最近遇到关于泛型的问题,网络资料很简陋,本人通过学习后整理出笔记分享出来。

写作背景

    争议巨大但同时万众期待的泛型终于在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 泛型全面讲解:一篇讲清泛型的全部

泛型的入门和基础使用

80520Go泛型快速入门

这个人很懒,什么都没留下

文章评论