泛型是goSDK1.18版本之后才引入的新特性,即C++中的模板。
为什么要有泛型?
我们现在要写一个两数相加的函数,相加的逻辑很简单,但是如果传入不同的类型,那么我们就需要再写一个函数,定义不同的参数类型,才能调用,go语言还没有函数重载,每次还要起不同的函数名,想想就很头疼哇。
明明逻辑都是一样的,仅仅因为参数类型不同就要重写一份,为了优化这种因素造成的代码冗余,因此我们可以使用泛型去简化代码。👇
package main
func main() {
a, b := 1, 2
c := Add1(a, b)
d, e :=1.1, 2.2
f :=Add2(d, e)
}
func Add1(a, b int) int {
return a + b
}
func Add2(a, b float64) float64 {
return a + b
}
func Add3(a, b int64) int64 {}
func Add4(a, b int8) int8 {}
所以泛型就是一种不限定参数的类型,让调用的人自己去定义类型的方法。
可能你会想到可以使用接口类型,但是下面我们看一下如果使用接口的话👇
package main
import "fmt"
func main() {
a, b := 1, 2
c, d := 2.2, 3.3
fmt.Println(Add(a, b))
fmt.Println(Add(c, d))
}
func Add(a, b interface{}) any {
if a1, ok := a.(int); ok {
if b1, ok := b.(int); ok {
return a1 + b1
}
}
if a1, ok := a.(float64); ok {
if b1, ok := b.(float64); ok {
return a1 + b1
}
}
return nil
//if {...}
//if {...}
//if {...}
}
可以看到,即使使用接口和类型断言之后,还需要加大量的类型断言判断代码,而且一个不注意还容易出错。
1、泛型的使用😁
package main
import "fmt"
func main() {
a, b := 1, 2
c, d := 1.1, 2.2
fmt.Println(Add[int](a, b))
fmt.Println(Add[float64](c, d))
}
// 这里救使用到了泛型🤗
func Add[T int | float64](a, b T) T {
return a + b
}
//泛型函数部分有更具体的😁😁😁
2、泛型类型😁
package main
import "fmt"
//使用type定义新类型
type s1 []int
type s2 []float64
type s3 []float32
func main() {
var a1 s1 = []int{1, 2, 3}
var a2 s2 = []float64{1, 2, 3}
var a3 s3 = []float32{1, 2, 3}
}
type可以定义新的类型,我们定义三个切片类型,3个切片内类型不同,因此就需要写3行type,定义3个新类型, 我们定义的结构都是一样的,只是它的类型不同,就需要重新定义这么多的类型。
有了泛型之后我们也可以改良这种代码。👇
package main
import "fmt"
//type s1 []int
//type s2 []float64
//type s3 []float32
// 这种类型的定义方式,带了类型形参,和普通定义类型就完全不同的。
// T 的类型可以进行约束,中间使用 |
// 我们定义的类型就是slice[T]
// T 说白了就是一个占位符,类型的形式参数,T是不确定的,需要在使用的时候进行传递。
type slice[T int32 | float64 | float32] []T //🐮🐮🐮
func main() {
// 传递类型参数🚩
var s1 slice[int32] = []int32{1, 2, 3, 4}
fmt.Println(s1)
var s2 slice[float64] = []float64{1, 2, 3, 4}
fmt.Println(s2)
var s3 slice[float32] = []float32{1, 2, 3, 4}
fmt.Println(s3)
//string不在T的约束内,定义的时候就会报错
//var s4 slice[string] = slice[string]{"1","2","3","4"}
}
更多定义例子
//😊😊😊😊😊
type myMap[KEY int | string, VALUE string] map[KEY]VALUE
type User[ID int, NAME string] struct{
id ID
name NAME
}
type I[T int | string] interface {
Hello1(a int)
Hello2(a string)
Hello3(a T)
}
type myChan[T int | string | float64] chan T
泛型可以定义在一切有类型的地方
3、泛型函数😁
泛型的主要使用就是和函数结合。
方法:
package main
import (
"fmt"
)
type MySlice[T int | float32 | int64] []T
func main() {
var s MySlice[int] = []int{1, 2, 3, 4}
fmt.Println(s.sum())
var s1 MySlice[float32] = []float32{1.0, 2.0, 3.0, 4.0}
fmt.Println(s1.sum())
}
//方法使用泛型
func (s MySlice[T]) sum() T {
var sum T
for _, v := range s {
sum += v
}
return sum
}
函数:
package main
import (
"fmt"
)
func main() {
var a int = 1
var b int = 2
fmt.Println(Add[int](a, b))
var c float32 = 1.1
var d float32 = 2.2
fmt.Println(Add[float32](c, d))
// Go的泛型语法糖:自动推导 (本质,就是编译器帮我们加上去了,在实际运行,这里T还是加上去的)
fmt.Println(Add(a, b)) // T : int
fmt.Println(Add(c, d)) // T : float32
}
func Add[T int | float32 | float64](a T, b T) T {
return a + b
}
4、自定义泛型😁
package main
// 泛型的约束提取定义
type My interface {
int|float32|int8|int32 // 作用域泛型的,而不是一个接口方法
}
// 自定义泛型
func main() {
var a int = 10
var b int = 20
GetMaxNum(a,b)
}
func GetMaxNum[T My](a,b T) T {
if a>b {
return a
}
return b
}
5、内置泛型类型😁
any :表示了go所有的内置类型
comparable :表示所有可以比较的类型
6、~(新符号)😁
package main
import "fmt"
// int8 衍生类型
type int8A int8
type int8B = int8
// ~ 表示可以匹配该类型的衍生类型
type NewInt interface {
~int8
}
// ~
func main() {
var a int8A = 10
var b int8A = 10
fmt.Println(GetMax(a, b))
}
func GetMax[T NewInt](a, b T) T {
if a > b {
return a
}
return b
}
7、泛型的作用😁
减少重复性的代码,提高安全性,针对不同的类型,写了相同逻辑的代码,我们就可以通过泛型来简化代码!