前言

本教程介绍 Go 中泛型的基础知识。使用泛型,你可以声明和使用编写为与调用代码提供的任何一组类型一起使用的函数或类型

在本教程中,你将声明两个简单的非泛型函数,然后在单个泛型函数中实现相同的逻辑

你将逐步完成以下部分:

  • 为你的代码创建一个文件夹。
  • 添加非泛型函数。
  • 添加一个通用函数来处理多种类型。
  • 调用泛型函数时删除类型参数。
  • 声明类型约束。

注意:有关其他教程,请参阅教程。

注意:如果你愿意,可以使用 “Go dev 分支”模式下的 Go Playground 来编辑和运行你的程序。

先决条件

Go 1.18

为你的代码创建一个文件夹

首先,为你要编写的代码创建一个文件夹。

  1. 打开命令提示符并切换到你的主目录。

在 Linux 或 Mac 上:

$ cd

在 Windows 上:

C:\> cd %HOMEPATH%

$Windows
generics
$ mkdir generics
$ cd generics

  1. 创建一个模块来保存你的代码。
go mod init
$ go mod init example/generics
go: creating new go.mod: module example/generics

注意:对于生产代码,你需要指定一个更符合你自己需求的模块路径。有关更多信息,请务必查看管理依赖项。

maps

添加非泛型函数

map
int64float64

编写代码

genericsmain.goGomain.go
package main

packagemain
  1. 在包声明下方,粘贴以下两个函数声明。
// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
    var s int64
    for _, v := range m {
        s += v
    }
    return s
}

// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
    var s float64
    for _, v := range m {
        s += v
    }
    return s
}

在此代码中,你:

SumFloatsstringfloat64mapSumIntsstringint64map
main.gomainmap
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("Non-Generic Sums: %v and %v\n",
        SumInts(ints),
        SumFloats(floats))
}

在此代码中,你:

float64int64
main.go

第一行代码应如下所示:

package main

import "fmt"

main.go

运行代码

main.go
$ go run .
Non-Generic Sums: 46 and 62.97

使用泛型,你可以在此处编写一个函数而不是两个。接下来,你将为包含整数或浮点值的映射添加一个通用函数。

添加通用函数来处理多种类型

在本节中,你将添加一个通用函数,该函数可以接收包含整数或浮点值的映射,从而有效地将你刚刚编写的两个函数替换为一个函数。

要支持任一类型的值,该单个函数将需要一种方法来声明它支持的类型。另一方面,调用代码需要一种方法来指定它是使用整数映射还是浮点映射

为了支持这一点,你将编写一个函数,该函数在其普通函数参数之外还声明类型参数。这些类型参数使函数具有通用性,使其能够处理不同类型的参数。你将使用类型参数和普通函数参数调用该函数。

每个类型参数都有一个类型约束,它充当类型参数的一种元类型。每个类型约束指定调用代码可用于相应类型参数的允许类型参数。

虽然类型参数的约束通常表示一组类型,但在编译时类型参数代表单一类型——调用代码作为类型参数提供的类型。如果类型参数的约束不允许类型参数的类型,则代码将无法编译。

请记住,类型参数必须支持泛型代码对其执行的所有操作。例如,如果你的函数代码尝试对其 string 约束包括数字类型的类型参数执行操作(例如索引),则代码将无法编译。

在你即将编写的代码中,你将使用允许整数或浮点类型的约束。

编写代码

  1. 在你之前添加的两个函数下方,粘贴以下通用函数。
// SumIntsOrFloats sums the values of map m. It supports both int64 and float64
// as types for map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

在此代码中,你:

SumIntsOrFloatsKVmmap[K]VVKcomparablecomparableGo==!=Gomap keysK as comparableKmapmap keysVint64float64|mtype map[K]VKVmap[K]VmapKKmap[K]V
main.go
fmt.Printf("Generic Sums: %v and %v\n",
    SumIntsOrFloats[string, int64](ints),
    SumIntsOrFloats[string, float64](floats))

在此代码中,你:

Go

运行代码

main.go
$ go run .
Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97

为了运行你的代码,在每次调用中,编译器将类型参数替换为该调用中指定的具体类型

在调用你编写的泛型函数时,你指定了类型参数,告诉编译器使用什么类型代替函数的类型参数。正如你将在下一节中看到的,在许多情况下你可以省略这些类型参数,因为编译器可以推断它们。

调用泛型函数时删除类型参数

在本节中,你将添加通用函数调用的修改版本,进行小的更改以简化调用代码。你将删除在这种情况下不需要的类型参数

Go

请注意,这并不总是可能的。例如,如果你需要调用没有参数的泛型函数,则需要在函数调用中包含类型参数

编写代码

main.go
fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
    SumIntsOrFloats(ints),
    SumIntsOrFloats(floats))

在此代码中,你:

  • 调用泛型函数,省略类型参数。

运行代码

main.go
$ go run .
Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97
Generic Sums, type parameters inferred: 46 and 62.97

接下来,你将通过将整数和浮点数的并集捕获到你可以重用的类型约束(例如从其他代码中)来进一步简化函数

声明类型约束

在最后一部分中,你将把之前定义的约束移到它自己的接口中,以便你可以在多个地方重用它。以这种方式声明约束有助于简化代码,例如当约束更复杂时。

你将类型约束声明为接口。约束允许任何类型实现接口。例如,如果你声明了具有三个方法的类型约束接口,然后在泛型函数中将其与类型参数一起使用,则用于调用该函数的类型参数必须具有所有这些方法。

正如你将在本节中看到的,约束接口也可以引用特定类型

编写代码

mainimport
type Number interface {
    int64 | float64
}

在此代码中,你:

Numberint64float64
int64float64Numberint64 | float64
SumNumbers
// 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))

在此代码中,你:

SumNumbersmapmap
Go

运行代码

main.go
$ go run .
Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97
Generic Sums, type parameters inferred: 46 and 62.97
Generic Sums with Constraint: 46 and 62.97

结论

做得很好!你刚刚向自己介绍了 Go 中的泛型。

建议的下一个主题:

GoGo

完整的代码

playground
package main

import "fmt"

type Number interface {
    int64 | float64
}

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("Non-Generic Sums: %v and %v\n",
        SumInts(ints),
        SumFloats(floats))

    fmt.Printf("Generic Sums: %v and %v\n",
        SumIntsOrFloats[string, int64](ints),
        SumIntsOrFloats[string, float64](floats))

    fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
        SumIntsOrFloats(ints),
        SumIntsOrFloats(floats))

    fmt.Printf("Generic Sums with Constraint: %v and %v\n",
        SumNumbers(ints),
        SumNumbers(floats))
}

// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
    var s int64
    for _, v := range m {
        s += v
    }
    return s
}

// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
    var s float64
    for _, v := range m {
        s += v
    }
    return s
}

// SumIntsOrFloats sums the values of map m. It supports both floats and integers
// as map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

// SumNumbers sums the values of map m. Its 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
}