亲宝软件园·资讯

展开

Golang泛型的使用方法详解

梦想画家 人气:0

Go 和Python语言不同,处理不同数据类型非常严格。如Python可以定义函数带两个数值类型并返回较大的数值,但可以不严格指定参数类型为float或integer。同样功能go1.18之前版本需要定义两个函数分别处理对应类型,通过泛型可以实现上面描述功能,无需为每种类型重复定义函数。

本文通过示例学习Go泛型,包括三个部分:非泛型函数,泛型函数,泛型类型约束。

泛型的作用

我们知道Go一些函数(如fmt.Println)使用空interface支持多种数据类型,但需要开发者写大量代码实现很多函数或方法支持多种自定义类型不是最佳选项,于是泛型登场,提供新的解决方案,无需使用接口和反射支持多种数据类型。

下面通过示例来说明:

package main
import (
    "fmt"
)
func Print[T any](s []T) {
    for _, v := range s {
        fmt.Print(v, " ")
    }
    fmt.Println()
}
func main() {
    Ints := []int{1, 2, 3}
    Strings := []string{"One", "Two", "Three"}
    Print(Ints)
    Print(Strings)
}

上面代码种Print()函数使用泛型,通过在函数名称和参数之间增加[T any]指定泛型变量, 既然T 为any类型,函数可传入任何数据类型slice。当然Print函数不能处理slice之外的输入,这是因为函数实现决定的。通过泛型函数可以避免针对每种数据类型编写函数,这种通用的思想就是泛型。

非泛型函数

我们实现两个函数,返回数值类型数组元素的和。我们至少创建两个函数,一个针对float64类型,另一个为int64,因此工作量翻倍。

package main
import "fmt"
func main() {
   f := []float64{1.0, 2.0, 3.0, 4.0, 5.0}
   i := []int64{1, 2, 3, 4, 5}
   s1 := SumOfFloat(f)
   s2 := SumOfIntegers(i)
   fmt.Println("Sum for float64 :", s1)
   fmt.Println("Sum for int64 :", s2)
}
func SumOfFloat(nums []float64) float64 {
   var sum float64
   for _, num := range nums {
       sum += num
   }
   return sum
}
func SumOfIntegers(nums []int64) int64 {
   var sum int64
   for _, num := range nums {
       sum += num
   }
   return sum
}

运行输出结果:

$ go run main.go
Sum for float64 : 15
Sum for int64 : 15

上面代码针对两种类型定义了两个函数,返回各自参数对应类型数组元素之和。下面通过泛型合并两个函数为一个,节约时间和精力。

泛型函数

本节实现单个函数,参数仍然是数组,但类型可以为float64或int64,并返回数组元素之和,从而可以代替上面两个函数。

为了能够定义函数接收float64和int64两种类型,新的泛型函数需要声明接收的类型,即调用代码需要判断类型是否为float64或int64。为此,新的函数签名需要有点变化,除了普通的参数外,还需要声明类型参数,从而转换函数为泛型函数支持不同类型。

每个形参都有一个类型约束,它指定调用代码可用于形参允许的实参类型。在编译时这些参数表示的类型就是调用代码支持的类型。当然泛型函数的实现逻辑需要支持形参声明的所有类型。举例,泛型函数支持字符串和数值参数,函数实现逻辑需要对参数进行索引,显然数值无法索引将引起编译错误。

泛型函数语法:

func genericFunction[T any](s []T) []T{

}

解释如下:

上面语法种在名称和参数之间的[]用于指定形式参数类型,可以是一组类型或一个约束接口。T 参数类型,用于定义形式参数和返回值类型。

函数内部也可以访问参数,any 是一个interfaceT必须实现该接口。Go1.18版本引入any,底层引用空接口,即interface{}。 下面举例说明:

package main
import "fmt"
func main() {
   f := []float64{1.0, 2.0, 3.0, 4.0, 5.0}
   i := []int64{1, 2, 3, 4, 5}
   s1 := genericSum(f)
   s2 := genericSum(i)
   fmt.Println("Sum for float64 :", s1)
   fmt.Println("Sum for int64 :", s2)
}
func genericSum[N int64 | float64](nums []N) N {
   var sum N
   for _, num := range nums {
       sum += num
   }
   return sum
}

输出结果:

$ go run main.go
Sum for float64 : 15
Sum for int64 : 15

上面示例中演示定义泛型函数genericSum。参数类型在[]中声明的泛型类型。N声明在形参和返回值中使用,N是新的类型,被限定了两种类型float64 和 int64的联合类型。

在函数体类,首先声明N类型变量,然后遍历数组计算综合并返回。

假设泛型函数支持很多种类型,比如:int, int8 , int26, int32, int64, float32, float64。这样函数签名变得非常冗长难看,我们可以定义一个类型包括上述类型,本质上就是把联合类型迁移至函数声明外面。

package main
import "fmt"
type Number interface {
   int64 | float64
}
func main() {
   f := []float64{1.0, 2.0, 3.0, 4.0, 5.0}
   i := []int64{1, 2, 3, 4, 5}
   s1 := genericSum(f)
   s2 := genericSum(i)
   fmt.Println("Sum for float64 :", s1)
   fmt.Println("Sum for int64 :", s2)
}
func genericSum[N Number](nums []N) N {
   var sum N
   for _, num := range nums {
       sum += num
   }
   return sum
}

总结

本文是关于Go泛型的,因为泛型是Golang的新成员。通过对比带泛型和不带泛型的函数,泛型可以很明显地节省时间和精力。泛型不是接口的替代品,两者被设计为一起工作,避免重复代码,让Go类型更安全、代码更整洁。

加载全部内容

相关教程
猜你喜欢
用户评论