第26章:泛型



泛型

在Go 1.18以前,Go只支持内置泛型。 从Go 1.18开始,Go也支持自定义泛型。 本文只介绍内置泛型。

Go通过各种一等公民组合类型来实现内置泛型。 我们可以用各种一等公民组合类型来组合出无穷个类型。 本文将展示一些自定义组合类型的例子并解释如何解读这些自定义类型。

一些复杂组合类型的例子

Go中的组合类型字面表示设计得非常直观和易于解读。 即使对于一些非常复杂的类型,我们也几乎不可能在解读它们的字面形式中迷失。 下面将从简单到复杂列出一些自定义组合类型的例子并进行解读。

先看一个简单的例子:

  1. [3][4]int

当解读一个类型的字面形式时,我们应该从左到右进行解读。 左边开头的[3]表示着这个类型为一个数组类型,它右边的整个部分为它的元素类型。 对于这个例子,它的元素类型为另外一个数组类型[4]int。 此另外一个数组类型的元素类型为内置类型int。 第一个数组类型可以被看作是一个二维数组类型。

一个使用此数组类型的例子:

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. matrix := [3][4]int{
  7. {1, 0, 0, 1},
  8. {0, 1, 0, 1},
  9. {0, 0, 1, 1},
  10. }
  11. matrix[1][1] = 3
  12. a := matrix[1] // 变量a的类型为[4]int
  13. fmt.Println(a) // [0 3 0 1]
  14. }

类似的,

  • [][]string是一个元素类型为另一个切片类型[]string的切片类型。
  • **bool是一个基类型为另一个指针类型*bool的指针类型。
  • chan chan int是一个元素类型为另一个通道类型的chan int的通道类型。
  • map[int]map[int]string是一个元素类型为另一个映射类型map[int]string的映射类型。 这两个映射类型的键值类型均为内置类型int
  • func(int32) func(int32)是一个只有一个输入参数和一个返回值的函数类型,此返回值的类型为一个只有一个输入参数的函数类型。 这两个函数类型的输入参数的类型均为内置类型int32

下面是另一个自定义组合类型:

  1. chan *[16]byte

最左边的chan关键字表明此类型是一个通道类型。 chan关键字右边的整个部分*[16]byte表示此通道类型的元素类型,此元素类型是一个指针类型。 此指针类型的基类型为*右边的整个部分:[16]byte,此基类型为一个数组类型。 此数组类型的元素类型为内置类型byte

一个使用此通道类型的例子:

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. "crypto/rand"
  6. )
  7. func main() {
  8. c := make(chan *[16]byte)
  9. go func() {
  10. // 使用两个数组以避免数据竞争。
  11. var dataA, dataB = new([16]byte), new([16]byte)
  12. for {
  13. _, err := rand.Read(dataA[:])
  14. if err != nil {
  15. close(c)
  16. } else {
  17. c <- dataA
  18. dataA, dataB = dataB, dataA
  19. }
  20. }
  21. }()
  22. for data := range c {
  23. fmt.Println((*data)[:])
  24. time.Sleep(time.Second / 2)
  25. }
  26. }

类似的,类型map[string][]func(int) int为一个映射类型。 此映射类型的键值类型为内置类型string,右边剩余的部分为此映射类型的元素类型。 []表明此映射的元素类型为一个切片类型,此切片类型的元素类型为一个函数类型func(int) int

一个使用了此映射类型的例子:

  1. package main
  2. import "fmt"
  3. func main() {
  4. addone := func(x int) int {return x + 1}
  5. square := func(x int) int {return x * x}
  6. double := func(x int) int {return x + x}
  7. transforms := map[string][]func(int) int {
  8. "inc,inc,inc": {addone, addone, addone},
  9. "sqr,inc,dbl": {square, addone, double},
  10. "dbl,sqr,sqr": {double, double, square},
  11. }
  12. for _, n := range []int{2, 3, 5, 7} {
  13. fmt.Println(">>>", n)
  14. for name, transfers := range transforms {
  15. result := n
  16. for _, xfer := range transfers {
  17. result = xfer(result)
  18. }
  19. fmt.Printf(" %v: %v \n", name, result)
  20. }
  21. }
  22. }

下面是一个看上去有些复杂的类型:

  1. []map[struct {
  2. a int
  3. b struct {
  4. x string
  5. y bool
  6. }
  7. }]interface {
  8. Build([]byte, struct {x string; y bool}) error
  9. Update(dt float64)
  10. Destroy()
  11. }

让我们从左到右解读此类型。 最左边开始的[]表明这是一个切片类型,紧跟着的map关键字表明此切片类型的元素为一个映射类型。 map关键字后紧跟的一对方括号[]中的结构体类型字面形式表明此映射的键值类型为一个结构体类型。 此中括号右边的整个部分表明此映射的元素类型为一个接口类型。此接口类型指定了三个方法。 此映射的键值结构体类型有两个字段,第一个字段的名称和类型为a和内置类型int; 第二个字段的名称为b,它的类型为另外一个结构体类型struct {x string; y bool}。 此另外一个结构体类型也有两个字段:内置string类型的字段x和内置bool类型的字段y

请注意第二个结构体类型也被用做刚提及的接口类型所指定的其中一个方法中的其中一个参数类型。

我们经常将复杂类型的各个组成部分单独提前声明为一个类型名,从而获得更高的可读性。 下面的代码中的类型别名T和上面刚解读的类型表示同一个类型。

  1. type B = struct {
  2. x string
  3. y bool
  4. }
  5. type K = struct {
  6. a int
  7. b B
  8. }
  9. type E = interface {
  10. Build([]byte, B) error
  11. Update(dt float64)
  12. Destroy()
  13. }
  14. type T = []map[K]E

内置泛型函数

Go中当前的内置泛型除了上述类型组合,还有一些支持泛型的内置函数。 比如,内置函数len可以用来获取各种容器值的长度。 unsafe标准库包中的函数也可以被看作是支持泛型的内置函数。 这些函数在前面的各篇文章中已经一一介绍过了。

自定义泛型

从1.18版本开始,Go已经支持自定义泛型。 请阅读《Go自定义泛型101》一书来了解如何使用自定义泛型。


本书由老貘历时三年写成。目前本书仍在不断改进和增容中。你的赞赏是本书和Go101.org网站不断增容和维护的动力。

(请搜索关注微信公众号“Go 101”或者访问github.com/golang101/golang101获取本书最新版)