内容目录

  1. 前提条件
  2. 创建代码目录
  3. 添加非泛型函数
  4. 添加一个处理多少类型的泛型函数
  5. 在调用泛型函数时移除类型参数

  6. 声明类型约束

  7. 结论
  8. 结束

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

在本教程中,您将声明两个简单的非泛型函数,然后在单个泛型函数中捕获相同的逻辑。

您将通过以下部分进行学习:

  1. 为代码创建一个文件夹。
  2. 增加非泛型函数。
  3. 添加一个泛型函数来处理多种类型。
  4. 在调用泛型函数时移除类型参数。
  5. 声明类型约束。

注意:关于其他教程,请参见教程
注意:如果你愿意,你可以使用“Go dev branch”模式下的Go playground来编辑和运行你的程序。

前提条件

  • Go 1.18或更高版本的安装。有关安装说明,请参见安装Go
  • 一个编辑代码的工具。任何文本编辑器都可以正常工作。
  • 一个命令终端。Go可以在Linux和Mac的任何终端上运行,在Windows的PowerShell或cmd上运行。

为代码创建一个文件夹

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

1.打开命令提示符并切换到您的主目录。
On Linux or Mac:

  1. $ cd

On Windows:

  1. C:\> cd %HOMEPATH%

本教程的其余部分将显示$作为提示符。你使用的命令也可以在Windows上使用

2.从命令提示符中,为您的代码创建一个名为泛型的目录。

  1. $ mkdir generics $ cd generics

3.创建一个module来保存代码。
运行go mod init 命令,给予代码目录。

  1. $ go mod init example/generics go: creating new go.mod: module example/generics

注意:对于生产代码,可以自己指定更适合的module路径。参见Managing dependencies.
接下来,您将添加一些简单的代码来处理映射。

Add non-generic functions

在这一步中,您将添加两个函数,每个函数都将一个map的值相加并返回总数。

声明两个函数而不是一个,因为要处理两种不同类型的映射:一种存储int64值,另一种存储float64值。

Write the code

  1. 使用文本编辑器,创建一个名为main的文件。进入泛型目录。您将在这个文件中编写Go代码。
  2. 为主要。在文件的顶部粘贴下面的包声明。

package main
一个独立的程序(相对于库)总是在包main中。

3.在包声明下面,粘贴以下两个函数声明。

  1. // SumInts adds together the values of m.
  2. func SumInts(m map[string]int64) int64 {
  3. var s int64
  4. for _, v := range m {
  5. s += v
  6. }
  7. return s
  8. }
  9. // SumFloats adds together the values of m.
  10. func SumFloats(m map[string]float64) float64 {
  11. var s float64
  12. for _, v := range m {
  13. s += v
  14. }
  15. return s
  16. }
  • 声明两个函数将一个map的值相加并返回和

    • SumFloats获取字符串/float64值的映射。

    • SumInts接受一个字符串/int64值的映射。

4.在包声明下面,粘贴以下主函数来初始化这两个映射,并在调用前面步骤中声明的函数时将它们用作参数。

  1. func main() {
  2. // Initialize a map for the integer values
  3. ints := map[string]int64{
  4. "first": 34,
  5. "second": 12,
  6. }
  7. // Initialize a map for the float values
  8. floats := map[string]float64{
  9. "first": 35.98,
  10. "second": 26.99,
  11. }
  12. fmt.Printf("Non-Generic Sums: %v and %v\n",
  13. SumInts(ints),
  14. SumFloats(floats))
  15. }

Add a generic function to handle multiple types

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

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

为了支持这一点,您将编写一个函数,在普通函数参数之外声明类型参数。这些类型参数使函数成为泛型,启用

Write the code

1.在前面添加的两个函数下面,粘贴下面的通用函数。

  1. // SumIntsOrFloats sums the values of map m. It supports both int64 and float64
  2. // as types for map values.
  3. func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
  4. var s V
  5. for _, v := range m {
  6. s += v
  7. }
  8. return s
  9. }
  • 声明一个SumIntsOrFloats函数,它有两个类型形参(在方括号内)K和V,还有一个实参使用类型形参m,其类型为map[K]V。函数返回类型为V的值。
  • 为K类型参数指定可比较的类型约束。专门针对这样的情况,在Go中预先声明了类似的约束。它允许任何类型的值可以用作比较操作符==和!=的操作数。Go要求map键具有可比性。声明K为comparable是必要的,这样你就可以在map变量中使用K作为键。它还确保调用代码为映射键使用允许的类型。
  • 为V类型参数指定一个int64和float64两种类型的并集约束。使用|指定两种类型的并集,这意味着该约束允许任何一种类型。这两种类型都被编译器允许作为调用代码中的参数。

  • 指定m参数的类型为map[K]V,其中K和V是已经为类型参数指定的类型。注意,我们知道map[K]V是一种有效的映射类型,因为K是一种可比较的类型。如果我们没有声明K是可比较的,编译器会拒绝对map[K]V的引用。

2.在已有的代码下面,粘贴以下代码。
fmt.Printf(“Generic Sums: %v and %v\n”, SumIntsOrFloatsstring, int64, SumIntsOrFloatsstring, float64)

  • 调用刚才声明的泛型函数,传递创建的每个映射.
  • 指定类型参数——方括号中的类型名——以便清楚地说明应该替换函数中类型参数的类型。在下一节中您将看到,通常可以在函数调用中省略类型参数。Go通常可以从你的代码中推断出它们。
  • 打印函数返回的和。

Run the code

从包含main的目录中的命令行。运行代码。

$ go run . Non-Generic Sums: 46 and 62.97 Generic Sums: 46 and 62.97
要运行代码,在每次调用中,编译器都会用该调用中指定的具体类型替换类型参数。

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

Remove type arguments when calling the generic function

在本节中,您将添加一个经过修改的泛型函数调用版本,进行一些小更改以简化调用代码。您将删除类型参数,这在本例中是不需要的。

当Go编译器可以推断出你想要使用的类型时,你可以在调用代码时省略类型参数。编译器从函数参数的类型推断类型参数。

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

Write the code

在已有的代码下面,粘贴以下代码

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

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

Run the code

从包含main的目录中的命令行。运行代码。

  1. $ go run .
  2. Non-Generic Sums: 46 and 62.97
  3. Generic Sums: 46 and 62.97
  4. Generic Sums, type parameters inferred: 46 and 62.97

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

Declare a type constraint

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

将类型约束声明为接口。约束允许任何类型实现接口。例如,如果使用三个方法声明一个类型约束接口,然后将其与泛型函数中的类型参数一起使用,则用于调用该函数的类型参数必须具有所有这些方法。
约束接口也可以引用特定的类型,您将在本节中看到。

Write the code

1.就在main上面,紧随import语句之后,粘贴以下代码来声明类型约束。

  • 声明Number接口类型作为类型约束使用。
  • 在接口内部声明int64和float64的并集。

本质上,您将联合从函数声明移到一个新的类型约束。这样,当您想要将类型参数约束为int64或float64时,可以使用这个Number类型约束,而不是写出int64 | float64。
2.在已有的函数下面,粘贴以下通用SumNumbers函数。

  1. // SumNumbers sums the values of map m. It supports both integers
  2. // and floats as map values.
  3. func SumNumbers[K comparable, V Number](m map[K]V) V {
  4. var s V
  5. for _, v := range m {
  6. s += v
  7. }
  8. return s
  9. }

使用与前面声明的泛型函数相同的逻辑声明泛型函数,但是使用新的接口类型而不是联合作为类型约束。与前面一样,使用类型参数作为实参并返回类型。

3.在已有的代码下面,粘贴以下代码。

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

调用每个map的SumNumbers,从每个map的值中输出和.

与前一节一样,在调用泛型函数时省略了类型参数(方括号中的类型名)。Go编译器可以从其他参数推断出类型参数。

Run the code

  1. $ go run .
  2. Non-Generic Sums: 46 and 62.97
  3. Generic Sums: 46 and 62.97
  4. Generic Sums, type parameters inferred: 46 and 62.97
  5. Generic Sums with Constraint: 46 and 62.97

Conclusion

很好地完成了!您刚刚向自己介绍了Go中的泛型

建议下一个话题:

Go Tour是一个很棒的一步一步介绍Go的基本原理。

你会在《Effective Go》和《如何编写Go代码》中找到有用的Go最佳实践。

Completed code

Go playground

  1. package main
  2. import "fmt"
  3. type Number interface {
  4. int64 | float64
  5. }
  6. func main() {
  7. // Initialize a map for the integer values
  8. ints := map[string]int64{
  9. "first": 34,
  10. "second": 12,
  11. }
  12. // Initialize a map for the float values
  13. floats := map[string]float64{
  14. "first": 35.98,
  15. "second": 26.99,
  16. }
  17. fmt.Printf("Non-Generic Sums: %v and %v\n",
  18. SumInts(ints),
  19. SumFloats(floats))
  20. fmt.Printf("Generic Sums: %v and %v\n",
  21. SumIntsOrFloats[string, int64](ints),
  22. SumIntsOrFloats[string, float64](floats))
  23. fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
  24. SumIntsOrFloats(ints),
  25. SumIntsOrFloats(floats))
  26. fmt.Printf("Generic Sums with Constraint: %v and %v\n",
  27. SumNumbers(ints),
  28. SumNumbers(floats))
  29. }
  30. // SumInts adds together the values of m.
  31. func SumInts(m map[string]int64) int64 {
  32. var s int64
  33. for _, v := range m {
  34. s += v
  35. }
  36. return s
  37. }
  38. // SumFloats adds together the values of m.
  39. func SumFloats(m map[string]float64) float64 {
  40. var s float64
  41. for _, v := range m {
  42. s += v
  43. }
  44. return s
  45. }
  46. // SumIntsOrFloats sums the values of map m. It supports both floats and integers
  47. // as map values.
  48. func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
  49. var s V
  50. for _, v := range m {
  51. s += v
  52. }
  53. return s
  54. }
  55. // SumNumbers sums the values of map m. Its supports both integers
  56. // and floats as map values.
  57. func SumNumbers[K comparable, V Number](m map[K]V) V {
  58. var s V
  59. for _, v := range m {
  60. s += v
  61. }
  62. return s
  63. }