今天开始,来学习Go 1.18引入的泛型,我们将以Go官方的泛型教程为资料,每天利用几分钟的时间来学习,慢慢积累。

很多编程语言都支持泛型的特性,泛型是对具体类型或属性的抽象替代,可以实现在编写代码和编译代码时无需知道其在运行时具体类型的特性。

根据Go官方的泛型教程,我们将先实现两个简单的未使用泛型的函数,随后将定义一个泛型函数实现相同的逻辑。

1.预备条件

  • 本地开发机安装Go 1.18
  • VS Code中配置使用Go 1.18(为了支持Go 1.18, 需要将VS Code的Go扩展升级到0.32.0+, gopls升级到0.8.0+, dlv升级到1.8.0+)

这两个预备条件,昨天已经准备好了。

接下来使用go mod创建一个练习项目。

1
2
3
mkdir generics
cd generics
go mod init example/generics

创建完成项目的go.mod文件如下:

1
2
3
module example/generics

go 1.18

注意一定要确保go mod文件中go的版本为1.18.

2.定义非泛型函数,实现简单逻辑

项目创建完成后,使用VS Code打开项目。

定义两个非泛型函数,分别实现将两个map类型map[string]int64map[string]float64中的值相加并返回。

因为要实现的逻辑比较简单,这两个函数的代码被放到的main.go源文件中main package中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import "fmt"

// 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
}

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))
}

因为要处理不同k-v类型的map,所以上面的代码定义了两个函数SumIntsSumFloats。 最后在用于测试的main函数中,初始化了一个map[string]int64的map和一个map[string]float64的map,调用前面定义的两个函数,并打印计算结果。

执行这段代码,输出如下:

1
2
go run .
Non-Generic Sums: 46 and 62.97

3.定义泛型函数,实现相同逻辑

接下来定义一个泛型函数,实现相同的逻辑,用来替代前面定义的两个非泛型函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import "fmt"

// 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
}

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))
}

SumIntsOrFloats函数在函数名称后边的紧跟一个中括号[K comparable, V int64 | float64]的定义了两个泛型参数K和V, 表示将在函数定义中使用这两个泛型参数。 在中括号中同时为泛型参数K和V指定了类型约束:

  • 为泛型参数K指定的类型约束是comparable,comparable是Go语言内置的类型约束,它表示类型的值可以使用==!=比较大小,这也是map类型的key要求的。
  • 为泛型参数V指定的类型约束是int64和float64两种类型的联合,|表示允许两者中的任何一个。

后边在函数的参数列表中将m参数的类型指定为map[K]V,因为在声明泛型参数时,已经为其指定了约束,例如Kcomparable的,这样map[K]V才是一个有效的map类型,如果没有指定这个约束,编译器会报编译错误。

执行这段代码,输出如下:

1
2
go run .
Generic Sums: 46 and 62.97

在main函数中调用了SumIntsOrFloats时,例如SumIntsOrFloats[string, int64](ints)通过中括号传入了泛型参数的类型,实际上在这个代码里传入泛型参数的类型是多余的,因为编译器可以自动推断。 因此,可以将main函数中调用SumIntsOrFloats的代码修改如下:

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

参考