12.泛型
Go语言在v1.18版本添加。其中泛即通用的意思。
12.1 泛型函数
如果没有泛型,同一种类型需要使用重载功能,但Go又不支持重载功能。如果写一个简单的加法计算函数,我们只能定义出如下所示的代码:
func AddInt(a, b int) int {
return a + b
}
func AddFloat(a, b float64) float64 {
return a + b
}
func JoinString(a, b string) string {
return a + b
}
从上面代码可以看出,每个函数的参数个数都一样,仅仅参数的数据类型不一样,如果还有类似的功能,仅仅是参数数据类型不一致,就还得增加函数,从而导致代码存在大量的重复。那么有没有一种,可以将参数的数据类型再抽象一次来解决这样的问题,我们来看看以下代码
func Add(a,b T) T{
return a+b
}
上面代码中的T表示类型形参,即参数的数据类型是可变的,a,b T则表明a和b的类型要一致。即在运行时,T最终一定会确定是某一种数据类型。Go语言针对这一问题的解决方案是泛型。其语法格式如下所示:
func name[T,P](parameters-1 T,parameters-2 P) P{
函数语句块
}
示例代码如下所示:
package main
import "fmt"
func Add[T int | float64 | string](a, b T) T {
return a + b
}
func main() {
fmt.Printf("int: %v,%[1]T\n", Add(4, 5))
fmt.Printf("float: %v,%[1]T\n", Add(4.7, 5.8))
fmt.Printf("string: %v,%[1]T\n", Add("Sur", "pass"))
}
运行结果如下所示:
int: 9,int
float: 10.5,float64
string: Surpass,string
通过以上代码,可以看到代码量大大减小,而且也非常简洁。
- T: 称为类型形参,仅仅是一个类型占位符
- int | float64 | string: 称为类型约束,其中
|表示或的意思 - T int | float64 | string:称为类型参数列表,存在多个时,使用逗号进行分隔,例如 [T int | string , P string | bool]
- Add[T]:即泛型函数
- Add[int]: 即类型实参,传入int给泛型函数的过程称为泛型函数实例化
- Add[int](4,5) 可以简写为 Add(4,5),因为可以根据传入的参数自动推断出数据类型
- 定义泛型函数时,函数名后面紧跟着类型参数列表。因此匿名函数不可以定义为泛型函数,但可通过使用定义的类型开通T
12.2 类型约束
类型约束是一个接口。为支持泛型,Go v1.18对接口进行了语法扩展。用在泛型中,接口含义就是符合这些特征的类型集合。在Go语言中,内置了两个约束:
- any: 表示任意类型
- comparable: 表示类型的值可以使用
==和!=进行比较
[T int ] 等价于[T interface{int}] // 表示T只能是int类型
type Constraint interface{
int | string
}
[T int | string ] 、 [ T interface{int | string }] 和 [T Constraint] 三者等价,都表示类型只能是int或string类型
12.3 泛型类型
示例代码如下所示:
package main
import "fmt"
// 接口从本质上讲,就是一种类型约束,如果要使用接口类型,则要求其结构必须接口中所有的方法
type Runer interface {
Run()
}
// 定义一种新的Map类型,要求Key的数据类型为int或string,而value为Runner类型
// [K string | int, V Runer] 对自定义类型的参数进行约束
type MyMap[K string | int, V Runer] map[K]V
type MyString string // 类型新定义
func (myString MyString) Run() {
fmt.Printf("MyString:%#v - %[1]T\n", myString)
}
func main() {
// var m MyMap[string, MyString] = make(MyMap[string, MyString])
d := make(MyMap[string, MyString]) //相当于map[string]MyString{}
fmt.Printf("d: %#v - %[1]T\n", d)
d["name"] = "Surpass"
fmt.Printf("d: %#v - %[1]T\n", d)
d["name"].Run()
}
运行结果如下所示:
d: main.MyMap[string,main.MyString]{} - main.MyMap[string,main.MyString]
d: main.MyMap[string,main.MyString]{"name":"Surpass"} - main.MyMap[string,main.MyString]
MyString:"Surpass" - main.MyString
结合以上代码,泛型是对函数的参数或返回值设置多个数据类型,比普通函数更灵活的设置参数类型和返回值类型,但却又比不上空接口参数的开放自由。
既然泛型比不上空接口,那为什么还要引入泛型呢?我们知道空接口参数不受数据类型限制,但如果在调用过程中,函数传入的参数是无法处理的数据类型,则很容易导致出现异常情况。而使用泛型时,既可以对函数的参数和返回值数据类型进行约束,也能保证传入参数的多样性。
本文同步在微信订阅号上发布,如各位小伙伴们喜欢我的文章,也可以关注我的微信订阅号:woaitest,或扫描下面的二维码添加关注:

作者: Surpassme
来源: http://www.jianshu.com/u/28161b7c9995/
http://www.cnblogs.com/surpassme/
声明:本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出 原文链接 ,否则保留追究法律责任的权利。如有问题,可发送邮件 联系。让我们尊重原创者版权,共同营造良好的IT朋友圈。

浙公网安备 33010602011771号