泛型入门
本教程介绍了Go中泛型(generics)的基础知识,并且将声明两个简单的非泛型函数,然后在单个泛型函数中捕获相同的逻辑。
一、前提
Go1.18
以及更高的版本
二、创建项目
创建一个名为 generics 的目录:
~$ mkdir generics
~$ cd generics
创建模块:
~/generics$ go mod init example/generics
go: creating new go.mod: module example/generics
三、调用非泛型函数
在此步骤中,将会添加两个函数,每个函数将map的值相加并返回总计。
注:这里需要声明两种不同类型的maps: 一个用于存储int64的值,一个用于存储float64的值。
接下来就是编写代码:
package main
import "fmt"
// SumInts takes a map of string to int64 values.
func SumInts(m map[string]int64) int64 {
var s int64
for _, v := range m {
s += v
}
return s
}
// SumFloats takes a map of string to float64 values.
func SumFloats(m map[string]float64) float64 {
var s float64
for _, v := range m {
s += v
}
return s
}
func main() {
// Initialize a map for the integer values
ints := map[string]int64{
"first": 34,
"second": 12,
}
// Initialize a map for the float values
floats := map[string]float64{
"first": 35.98,
"second": 26.99,
}
// Call the two functions you declared earlier to find the sum of each map’s values
// and print the result
fmt.Printf("Non-Generic Sums: %v and %v\n",
SumInts(ints),
SumFloats(floats))
}
运行代码:
~/generics$ go run .
Non-Generic Sums: 46 and 62.97
四、调用泛型函数处理多种类型
在此步骤中,将通过编写一个泛型函数,该函数可以接受包含int或者float的map。
要想实现该功能,有以下几点需要考虑:
- 支持任意类型: 需要一种方法来声明支持的类型
- 判断类型: 判断它是使用int或者flaot的map。
- 处理类型: 除了普通的函数参数依赖还需要有
type
参数,使得能够处理不同类型的参数。可以通过type
参数和普通参数一起来调用函数。 type
类型约束: 每个type参数都有一个type约束,指定调用代码可用的type参数。如果type
参数的约束不允许指定的type
参数,则代码将无法编译。
注: type参数必须支持泛型代码对其执行的所有操作。
接下来就是编写代码:
package main
import "fmt"
// SumIntsOrFloats sums the values of map m. It supports both int64 and float64
// as types for map values.
// Declare a SumIntsOrFloats function with two type parameters (inside the square brackets), K and V
// and one argument that uses the type parameters, m of type map[K]V. The function returns a value of type V.
// Specify for the K type parameter the type constraint comparable.the comparable constraint is predeclared in Go. It allows any type whose values may be used as an operand of the comparison operators == and !=.
// Specify for the V type parameter a constraint that is a union of two types: int64 and float64. Using | specifies a union of the two types, meaning that this constraint allows either type.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
func main() {
// Initialize a map for the integer values
ints := map[string]int64{
"first": 34,
"second": 12,
}
// Initialize a map for the float values
floats := map[string]float64{
"first": 35.98,
"second": 26.99,
}
fmt.Printf("Generic Sums: %v and %v\n",
SumIntsOrFloats[string, int64](ints),
SumIntsOrFloats[string, float64](floats))
}
运行代码:
~/generics$ go run .
Generic Sums: 46 and 62.97
五、不使用类型参数调用泛型函数
当 Go 编译器可以推断出您要使用的类型时,可以在调用代码中省略类型参数。编译器从函数参数的类型推断类型参数。
请: 如果需要调用没有参数的泛型函数,则需要在函数调用中包含类型参数。
修改上面的代码中的打印部分代码:
fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
SumIntsOrFloats(ints),
SumIntsOrFloats(floats))
结果和上面一致。
六、声明类型约束为接口
将类型约束声明为一个接口。
// Declare the Number interface type to use as a type constraint.
type Number interface {
// Declare a union of int64 and float64 inside the interface.
int64 | float64
}
这样,当想要将类型参数约束为 int64
或 float64
时,可以使用此 Number
类型约束,而不是写出 int64 |float64
。
之后在该接口粘贴如下代码:
// SumNumbers sums the values of map m. It supports both integers
// and floats as map values.
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("Generic Sums with Constraint: %v and %v\n",
SumNumbers(ints),
SumNumbers(floats))
运行代码:
~/generics$ go run .
Generic Sums with Constraint: 46 and 62.97