在Go 1.18以前,Go只支持内置泛型。 从Go 1.18开始,Go也支持自定义泛型。 本文只介绍内置泛型。
Go通过各种一等公民组合类型来实现内置泛型。 我们可以用各种一等公民组合类型来组合出无穷个类型。 本文将展示一些自定义组合类型的例子并解释如何解读这些自定义类型。
Go中的组合类型字面表示设计得非常直观和易于解读。 即使对于一些非常复杂的类型,我们也几乎不可能在解读它们的字面形式中迷失。 下面将从简单到复杂列出一些自定义组合类型的例子并进行解读。
先看一个简单的例子:[3][4]int
当解读一个类型的字面形式时,我们应该从左到右进行解读。
左边开头的[3]
表示着这个类型为一个数组类型,它右边的整个部分为它的元素类型。
对于这个例子,它的元素类型为另外一个数组类型[4]int
。
此另外一个数组类型的元素类型为内置类型int
。
第一个数组类型可以被看作是一个二维数组类型。
package main
import (
"fmt"
)
func main() {
matrix := [3][4]int{
{1, 0, 0, 1},
{0, 1, 0, 1},
{0, 0, 1, 1},
}
matrix[1][1] = 3
a := matrix[1] // 变量a的类型为[4]int
fmt.Println(a) // [0 3 0 1]
}
类似的,
[][]string
是一个元素类型为另一个切片类型[]string
的切片类型。
**bool
是一个基类型为另一个指针类型*bool
的指针类型。
chan chan int
是一个元素类型为另一个通道类型的chan int
的通道类型。
map[int]map[int]string
是一个元素类型为另一个映射类型map[int]string
的映射类型。
这两个映射类型的键值类型均为内置类型int
。
func(int32) func(int32)
是一个只有一个输入参数和一个返回值的函数类型,此返回值的类型为一个只有一个输入参数的函数类型。
这两个函数类型的输入参数的类型均为内置类型int32
。
下面是另一个自定义组合类型:
chan *[16]byte
最左边的chan
关键字表明此类型是一个通道类型。
chan
关键字右边的整个部分*[16]byte
表示此通道类型的元素类型,此元素类型是一个指针类型。
此指针类型的基类型为*
右边的整个部分:[16]byte
,此基类型为一个数组类型。
此数组类型的元素类型为内置类型byte
。
package main
import (
"fmt"
"time"
"crypto/rand"
)
func main() {
c := make(chan *[16]byte)
go func() {
// 使用两个数组以避免数据竞争。
var dataA, dataB = new([16]byte), new([16]byte)
for {
_, err := rand.Read(dataA[:])
if err != nil {
close(c)
} else {
c <- dataA
dataA, dataB = dataB, dataA
}
}
}()
for data := range c {
fmt.Println((*data)[:])
time.Sleep(time.Second / 2)
}
}
类似的,类型map[string][]func(int) int
为一个映射类型。
此映射类型的键值类型为内置类型string
,右边剩余的部分为此映射类型的元素类型。
[]
表明此映射的元素类型为一个切片类型,此切片类型的元素类型为一个函数类型func(int) int
。
package main
import "fmt"
func main() {
addone := func(x int) int {return x + 1}
square := func(x int) int {return x * x}
double := func(x int) int {return x + x}
transforms := map[string][]func(int) int {
"inc,inc,inc": {addone, addone, addone},
"sqr,inc,dbl": {square, addone, double},
"dbl,sqr,sqr": {double, double, square},
}
for _, n := range []int{2, 3, 5, 7} {
fmt.Println(">>>", n)
for name, transfers := range transforms {
result := n
for _, xfer := range transfers {
result = xfer(result)
}
fmt.Printf(" %v: %v \n", name, result)
}
}
}
下面是一个看上去有些复杂的类型:
[]map[struct {
a int
b struct {
x string
y bool
}
}]interface {
Build([]byte, struct {x string; y bool}) error
Update(dt float64)
Destroy()
}
让我们从左到右解读此类型。
最左边开始的[]
表明这是一个切片类型,紧跟着的map
关键字表明此切片类型的元素为一个映射类型。
map
关键字后紧跟的一对方括号[]
中的结构体类型字面形式表明此映射的键值类型为一个结构体类型。
此中括号右边的整个部分表明此映射的元素类型为一个接口类型。此接口类型指定了三个方法。
此映射的键值结构体类型有两个字段,第一个字段的名称和类型为a
和内置类型int
;
第二个字段的名称为b
,它的类型为另外一个结构体类型struct {x string; y bool}
。
此另外一个结构体类型也有两个字段:内置string
类型的字段x
和内置bool
类型的字段y
。
请注意第二个结构体类型也被用做刚提及的接口类型所指定的其中一个方法中的其中一个参数类型。
我们经常将复杂类型的各个组成部分单独提前声明为一个类型名,从而获得更高的可读性。 下面的代码中的类型别名T
和上面刚解读的类型表示同一个类型。
type B = struct {
x string
y bool
}
type K = struct {
a int
b B
}
type E = interface {
Build([]byte, B) error
Update(dt float64)
Destroy()
}
type T = []map[K]E
Go中当前的内置泛型除了上述类型组合,还有一些支持泛型的内置函数。
比如,内置函数len
可以用来获取各种容器值的长度。
unsafe
标准库包中的函数也可以被看作是支持泛型的内置函数。
这些函数在前面的各篇文章中已经一一介绍过了。
从1.18版本开始,Go已经支持自定义泛型。 请阅读《Go自定义泛型101》一书来了解如何使用自定义泛型。
Go101.org网站内容包括Go编程各种相关知识(比如Go基础、Go优化、Go细节、Go实战、Go测验、Go工具等)。后续将不断有新的内容加入。敬请收藏关注期待。
本丛书微信公众号(联系方式一)名称为"Go 101"。二维码在网站首页。此公众号将时不时地发表一些Go语言相关的原创短文。各位如果感兴趣,可以搜索关注一下。
《Go语言101》系列丛书项目目前托管在Github上(联系方式二)。欢迎各位在此项目中通过提交bug和PR的方式来改进完善《Go语言101》丛书中的各篇文章。我们可以在项目目录下运行go run .
来浏览和确认各种改动。
本书的twitter帐号为@Golang_101(联系方式三)。玩推的Go友可以适当关注。
你或许对本书作者老貘开发的一些App感兴趣。
sync
标准库包sync/atomic
标准库包