泛型

在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]
}

类似的,

下面是另一个自定义组合类型:
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感兴趣。

The English version of this book is here.
赞赏
(《Go语言101》系列丛书由老貘从2016年7月开始编写。目前此系列丛书仍在不断改进和增容中。你的赞赏是本系列丛书和此Go101.org网站不断增容和维护的动力。)

目录: