这篇文章介绍Go中基本的泛型。使用泛型,你能声明和使用函数或者类型,它们被写用来和被调用者代码提供的任意集合类型一起工作。
在这篇指导中,你将声明两个简单的非泛型函数,然后在单个泛型函数中获取一些逻辑。
你将通过下面的部分进行处理:
为了开始,创建一个目录为你将要写的代码。
打开命令行终端,切换到主目录
$ cd
$
通过命令行,为你的代码创建一个目录,叫做generics
$ cd generics/
$ cd generics
$
创建一个模块用于持有你的代码
$ go mod init example/generics
go: creating new go.mod: module example/generics
$
注意,对于生产代码,你会指定一个更符合你自己需求的模块路径。
接下来,你会添加一些代码,用于处理maps。
在这一步中,您将添加两个函数,每个函数将map的值相加并返回总数。
你将声明两个函数,而不是一个,因为你将使用两种不同的数据类型去工作:一个存储int64
值,一个存储float64
值。
使用你的编译器,在generics
文件夹中创建一个main.go
文件。你将在这个文件中写代码。
进入main.go
,在文件的顶部,粘贴下面的包声明:
package main
一个独立的程序总是在main
包中。
在包声明下面,粘贴下面的两个函数声明
// SumInts 将m的值添加在一起
func SumInts(m map[string]int64) int64 {
var s int64
for _,v := range m {
s += v
}
return s
}
// SumFloats 将m的值添加在一起
func SumFloats(m map[string]float64) float64 {
var s float64
for _, v := range m {
s += v
}
return s
}
在这个代码中:
SumFloats
获取一个map: String到float64 的值;SumInts
获取一个map: String到int64
的值;在main.go
文件的顶部,包声明的下面,粘贴下面的main函数,去初始化两个map,并使用它们作为参数,调用你在前面步骤中声明的函数
func main() {
// 为Int 值初始化一个map
ints := map[string]int64 {
"first" : 34,
"second" : 21,
}
// 为float值 初始化一个map
floats := map[string]float64 {
"first" : 35.98,
"second" : 26.99,
}
fmt.Printf("非泛型 Sums:%v and %v\n", SumInts(ints), SumFloats(floats))
}
在这个代码中:
float64
和一个int64的map,每一个都有两项;在靠近main.go
的顶部,仅仅在包声明的下面,导入需要支持你刚刚写的代码的包
第一行代码看起来像这样:
package main
import "fmt"
保存main.go
文件
在包含main.go
文件的目录的命令行,运行代码:
$ go run .
非泛型 Sums:55 and 62.97
$
使用泛型,你可以写一个函数代替两个。接下来,你将添加一个泛型函数,map既可以是float,又可以是integer。
在这一部分,你将添加一个单个泛型函数,它可以接收包含integer或float值的map。有效的使用单个函数替换了你刚刚写的两个函数。
为了支持两种数据类型,单个函数需要有一种方法声明它支持的数据类型。调用代码,另一个方面,将需要一种方法明确它正在调用一个integer的map或一个float的map。
为了支持这一点,您将编写一个除了普通函数参数之外还声明类型参数的函数。这种类型参数让函数泛型化,使其能够处理不同类型的参数。你将使用类型参数和普通的函数参数来调用函数。
每个类型参数都有一个类型的约束。充当类型参数的元类型。每一个类型约束指明了允许的类型参数,调用代码能够使用,对于各自的类型参数。
尽管一个类型参数的约束通常代表着一个类型集合。在编译的时候,编译时类型参数代表单一类型——类型被提供做为被调用代码的类型参数。如果类型参数的约束不允许类型参数的类型,代码将不能编译。
记住,类型参数必须支持泛型代码对其执行的所有操作。例如,如果你的函数试着在类型参数上进行string操作(例如索引),它如果包含数值类型,代码将不能编译。
在你将写的代码中,你将使用一个约束,即允许integer类型,又允许float类型。
在上一部分你添加的两个函数下面,粘贴下面的泛型函数。
// SumIntsOrFloat 获取m值的和。它既支持int64类型,也支持float64类型
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
在这个代码中:
SumIntsOrFloats
函数,带有两个类型参数(在中括号里面),K
和V
,和一个参数,使用的参数,一个类型为map[K]V
的m
。函数返回一个V
类型的值。comparable
。专门针对此类型,comparable
约束,是Go的预定义约束。它允许任何类型的值能够被用于作为比较运算符==
和!=
的操作数。Go需要map的key是可比较的。因此声明K
是comparable
是必须的,以便于你能使用K作为key在map变量中。它还确保调用代码对映射键使用允许的类型。V
类型参数一个约束,是两种类型的并集:int64 和 float64。使用|
指明两种类型的并集。意味着这种约束允许两种类型的任意个。编译器将允许任一类型作为调用代码中的参数。map[K]V
,K和V就是指定的类型参数。注意,我们知道map[K]V
是一个有效的map类型,因为K是一个可比较类型。如果我们没有声明K为comparable
,编译器将拒绝指向map[K]V
的引用。在main.go
文中,在你已经写的代码下面,粘贴下面的代码
fmt.Printf("泛型求和: %v 和 %v\n", SumIntsOrFloats[string, int64](ints), SumIntsOrFloats[string, float64](floats))
在这个代码中:
调用泛型函数,传递你创造的map;
指定了类型参数:类型的名称在中括号里。清楚在你要调用的函数中,要替换的类型参数。
就像你在下部分将看到的,在函数调用中,你通常省略类型参数。Go能够从你的代码中推断。
打印从函数返回的和。
在包含main.go
文件目录的命令行下,运行代码:
$ go run .
非泛型 Sums:55 and 62.97
泛型求和: 55 和 62.97
$
为你运行你的代码,在每次调用中,编译器将使用调用中的具体的类型替换类型参数。
在调用你写的泛型函数时,你指定了类型参数。它告诉编译器,什么类型将替换函数的类型参数。正如你在下部分看到的,很多场景下,你能忽略这些类型参数,因为编译器能推断它们。
在这一部分,你将添加一个修改版本的泛型参数调用,制造一个聪明的改变去简化调用代码。你将移除类型参数,它们在这些场景下不需要。
当Go编译器能够推断你使用的类型参数时,你可以忽略类型参数。编译器从函数参数类型推断类型参数。
注意,它并不是总是可能的。例如,如果你需要调用没有参数泛型函数。你在函数调用的时候,需要包含类型参数。
在main.go
文件中,在上面的代码下面,粘贴下面的代码:
fmt.Printf("泛型求和,推断类型参数: %v 和 %v\n", SumIntsOrFloats(ints), SumIntsOrFloats(floats))
在这个代码中:
在包含main.go
文件的目录下,运行下面的代码:
$ go run .
非泛型 Sums:55 and 62.97
泛型求和: 55 和 62.97
泛型求和,推断类型参数: 55 和 62.9
$
在最后一部分,你将移动你早期定义的约束到你自己的接口中,以便于你重复使用它们在多个地方。以这种方式声明约束有助于简化代码,例如当约束更复杂时。
你能声明一个类型约束作为一个接口。这个约束允许任何类型实现接口。例如,如果你声明一个类型约束接口,带有三个方法。那么使用它,在一个带有一个类型参数的泛型函数中,用于调用函数的类型参数必须具有所有这些方法。
约束接口也能指向特定的类型,就像你在这部分看到的。
在main
函数上面,导入语句下面,粘贴下面的类型参数去声明一个类型约束。
type Number interface {
int64 | float64
}
在代码中:
在你已经声明的函数下面,粘贴下面的泛型函数
// SumNumbers 对m的值求和。它既支持integer,又支持floats 作为map的值
func SumNumbers[K comparable, V Number](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
在这个代码中:
在main.go
文件中,在已经存在的代码下面,粘贴下面的代码:
fmt.Printf("泛型求和,使用约束: %v 和 %v\n", SumNumbers(ints), SumNumbers(floats))
在这个代码中:
对每一个map,调用SumNumbers
,打印每个值
就像上一部分,在调用泛型函数的时候省略了类型参数。编译器能够推断出类型参数。
在包含main.go
文件目录的命令行下,运行代码:
$ go run .
非泛型 Sums:55 and 62.97
泛型求和: 55 和 62.97
泛型求和,推断类型参数: 55 和 62.97
泛型求和,使用约束: 55 和 62.97
$