16.1 泛型的非正式介绍

无论语言是否正式支持泛型,静态和强类型语言的类型系统都内置支持泛型(例如,Go 1.18之前与之后)。

例如,正如我们在前面章节中简要提到的,无论我们是否这样称呼它们,Go的内置集合类型array、slice、map和chan都是泛型类型。

举个例子,让我们考虑创建一系列int数组类型,大小分别为10、11、12和更多。

type intArr10 ... // 10个元素的int数组的类型
type intArr11 ... // 11个元素的int数组的类型
type intArr12 ... // 12个元素的int数组的类型
// ...

在Go中,intArr10、intArr11和intArr12是不同的、独特的类型,然而它们彼此看起来非常相似。唯一的区别是数组中的元素数量。这样显式地创建几十个甚至几个int数组类型通常是很不方便的。Go的内置数组文字语法支持创建由它们的大小参数化的数组类型。

type intArr10 [10]int
type intArr11 [11]int
type intArr12 [12]int
// ...

这个例子有点复杂,但重点是我们并不总是必须创建像intArr10、intArr11和intArr12这样的多种类型。Go的内置语法[N]int支持创建这个(可能无限的)相关类型系列,例如[10]int、[11]int、[1000]int等(其中N==10、N==11、N==1000等)。这就是泛型。

Go还允许创建具有不同元素类型的数组类型。语法上,我们可以(非正式地)将其写成[N]T,其中T代表任意类型。注意,像[100]T这样的“泛型类型”现在由另一种类型参数化,即其元素类型。(它表示元素类型为T的100个元素数组类型的集合)。Go的(新的)泛型类型或参数化类型(由另一种类型参数化)使用不同的语法,但思想是相同的。

“泛型类型”定义了一组相关(真实/具体)类型。尽管有这个名字,泛型类型并不是“真实类型”。它就像真实/具体类型的模板。例如,您不能直接在Go程序中使用map类型。您只能使用具体类型,如map[int]string。 Go的内置“map泛型类型”可以用map[K]V语法来表示(再次非正式),其中K和V分别表示键和值类型。

语法上的差异可能有点令人困惑,但与内置集合类型不同,Go允许使用特殊的泛型类型语法创建泛型类型和泛型函数(即使用泛型类型作为其参数和/或返回值的函数)。

对于泛型类型,可以使用命名类型定义语法创建泛型类型:

type TypeName[T1 Constraint1, T2 Constraint2, ...] AnotherType

在这里,AnotherType可以是命名或匿名类型(如struct或interface类型),可以依赖于泛型类型参数,例如T1、T2等。

对于泛型函数,可以使用泛型函数声明语法创建依赖于泛型类型的函数:

func FunctionName[T1 Constraint1, T2 Constraint2, ...]
  (/* 输入参数列表 */)
  /* 返回参数列表 */
{
  // 函数体语句列表
}

在泛型函数声明中,InputParameterList和/或ReturnParameterList可以包括泛型类型参数(例如T1、T2等),代替真实/具体类型。同样,函数体可以将类型参数当作真实的具体类型使用,只要它们的类型约束与实现一致即可。

有几件事需要注意。

  • 根据定义,一个容器类型是一个参数化的类型。容器包括(特定类型的)元素。容器类型是最简单的,也是最重要的泛型的用例。

  • 在Go的泛型语法中,总是需要类型参数的约束,即使它是any。

  • 请注意,在内置的map类型中,键值必须是 "可散列 "的。这个约束是隐含的。另一方面,如果我们定义一个自定义的map/dictionary泛型,我们可以明确地指定这个要求(至少在理论上是这样)。

  • 目前,从Go 1.19开始,只有两个接口,即any和comparable,被内置到语言中。预计在不久的将来,更多的接口(可以作为类型约束)将被包含在标准库中。

  • Go的泛型在各方面都有一定的限制。例如,你不能定义一个依赖整数值的泛型(例如,像内置的数组类型)。

最后更新于