Go 泛型
最后修改于 2025 年 5 月 5 日
在本文中,我们将介绍如何在 Golang 中使用泛型。
Go 中的泛型使开发人员能够编写灵活且可重用的代码,这些代码可以处理多种数据类型。通过使用泛型,可以以允许它们处理任何指定类型集的方式定义函数和类型,从而减少冗余并提高可维护性。
Go 通过参数化类型来实现泛型,这些类型定义在方括号 [] 内。这些类型参数允许开发人员在保持灵活性的同时指定约束。
func Println[T any](v T) { fmt.Println(v) }
在此示例中,Println 函数接受一个泛型类型参数 T。any 关键字用作约束,表示 T 可以是任何类型。调用时,该函数会打印其参数,同时保持类型安全。
根据约定,泛型参数类型用大写字母表示,最常用的是 T 表示通用类型,K 表示键,V 表示值。此标准提高了 Go 程序的可读性和一致性。
当设计需要类型灵活性但又要保持强类型和效率的库、数据结构和算法时,泛型尤其有用。
Go 泛型示例
在第一个示例中,我们定义了一个简单的泛型函数。
package main
import (
"fmt"
)
func Println[T any](v T) {
fmt.Println(v)
}
func main() {
Println[string]("an old falcon")
Println[int](23)
Println[float64](3.34)
Println[bool](true)
Println[[]int]([]int{1, 2, 3, 4, 5})
}
我们的自定义 Println 函数可以接受任何类型并打印它。
Println[string]("an old falcon")
Println[int](23)
Println[float64](3.34)
Println[bool](true)
Println[[]int]([]int{1, 2, 3, 4, 5})
我们打印字符串值、int64 值、float64 值、bool 值和整数切片值。
$ go run main.go an old falcon 23 3.34 true [1 2 3 4 5]
Go 泛型省略类型
在许多情况下,我们可以省略调用泛型函数时的类型。编译器将从函数参数推断出类型。
package main
import (
"fmt"
)
func Println[T any](v T) {
fmt.Println(v)
}
func main() {
Println("an old falcon")
Println(23)
Println(3.34)
Println(true)
Println([]int{1, 2, 3, 4, 5})
}
在程序中,我们在调用 Println 函数时省略了参数类型声明。
$ go run main.go an old falcon 23 3.34 true [1 2 3 4 5]
Go 泛型联合类型
我们可以将泛型类型参数限制为联合中的类型。
package main
import (
"fmt"
)
func Println[T string | int](v T) {
fmt.Println(v)
}
func main() {
Println("an old falcon")
Println(23)
// Println(3.34)
// Println(true)
// Println([]int{1, 2, 3, 4, 5})
}
在程序中,Println 函数可以接受字符串或整数。
$ go run main.go an old falcon 23
Go 泛型波浪线
~ 波浪线标记用于 ~T 的形式,表示底层类型为 T 的类型集合。
package main
import (
"fmt"
)
type mystring string
func Println[T ~string | int](v T) {
fmt.Println(v)
}
func main() {
Println("an old falcon")
Println(23)
Println(mystring("rainy day"))
}
在程序中,我们有一个自定义的 mystring 类型。使用 ~string 语法,我们告诉编译器包含任何近似于 string 的类型。
$ go run main.go an old falcon 23 rainy day
Go 泛型过滤函数
filter 函数处理一个集合,并生成一个新集合,其中只包含给定谓词返回 true 的元素。
在下一个示例中,我们创建了一个泛型版本的 filter 函数。
package main
import (
"fmt"
"strings"
)
func filter[T any](data []T, f func(T) bool) []T {
fltd := make([]T, 0, len(data))
for _, e := range data {
if f(e) {
fltd = append(fltd, e)
}
}
return fltd
}
func main() {
words := []string{"war", "cup", "water", "tree", "storm"}
res := filter(words, func(s string) bool {
return strings.HasPrefix(s, "w")
})
fmt.Println(res)
vals := []int{-1, 0, 2, 5, -9, 3, 4, 7}
res2 := filter(vals, func(e int) bool {
return e > 0
})
fmt.Println(res2)
}
在程序中,我们使用泛型 filter 函数来过滤字符串和整数。
func filter[T any](data []T, f func(T) bool) []T {
fltd := make([]T, 0, len(data))
for _, e := range data {
if f(e) {
fltd = append(fltd, e)
}
}
return fltd
}
filter 函数构建一个新切片,其中仅包含满足给定条件的元素。该函数作用于类型参数 T,约束为 any。它接受集合和谓词函数作为参数。我们对每个元素调用谓词,如果它满足谓词的条件,则将其添加到 fltd 切片中。
res := filter(words, func(s string) bool {
return strings.HasPrefix(s, "w")
})
这里我们过滤掉所有以“w”开头的单词。
$ go run main.go [war water] [2 5 3 4 7]
Go 泛型 ForEach 函数
在下一个程序中,我们创建了一个泛型 ForEach 函数。
package main
import "fmt"
func ForEach[T any](data []T, f func(e T, i int, data []T)) {
for i, e := range data {
f(e, i, data)
}
}
func main() {
vals := []int{-1, 0, 2, 1, 5, 4}
ForEach(vals, func(e int, i int, data []int) {
fmt.Printf("e at %d: %d\n", i, e)
})
fmt.Println("-------------------------")
words := []string{"sky", "forest", "word", "cup", "coin"}
ForEach(words, func(e string, i int, data []string) {
fmt.Printf("e at %d: %s\n", i, e)
})
}
泛型 ForEach 函数将泛型切片和闭包函数作为参数。闭包用于对每个元素执行任务。
func ForEach[T any](data []T, f func(e T, i int, data []T)) {
for i, e := range data {
f(e, i, data)
}
}
在 ForEach 函数中,我们使用 for 循环遍历泛型切片的元素,并对每个元素调用闭包。
ForEach(vals, func(e int, i int, data []int) {
fmt.Printf("e at %d: %d\n", i, e)
})
在实际调用 ForEach 函数时,我们传递一个具有具体类型的闭包。元素类型为 int,索引类型为 int,集合类型为 int[]。
$ go run main.go e at 0: -1 e at 1: 0 e at 2: 2 e at 3: 1 e at 4: 5 e at 5: 4 ------------------------- e at 0: sky e at 1: forest e at 2: word e at 3: cup e at 4: coin
来源
在本文中,我们涵盖了 Golang 中的泛型。