什么是泛型
Go并不是一种静止、一成不变的编程语言。新的功能是在经过大量的讨论和试验后慢慢采用的
最初的Go1.0发布以来,Go语言习惯的模式已经发生了重大变化
1.7的context.1.11的modules、1.13erro嵌套等
Go的1.18版本包括了参数类型参数的实现,也就是俗称的泛型
泛型虽然很受期待,但实际上推荐的使用场景也并没有那么广泛
但是我们作为学习者,一定要了解学会,至少遇到了不懵逼
package main
import "fmt"
func main() {
//2、创建打印输出,但会报错,将其注释后
str := []string{"xuexiangban", "kuanshenshuo"}
printarr(str) //cannot use str (variable of type []string) as type []interface{} in anrgument to printarrp无法使用str(类型[]字符串的变量)作为类型[]接口{}在打印程序中
printarr2(str)
is := []int{1, 2, 3}
//printarr(is) //cannot use is (variable of type []int) as type []interface{} in argument to printanp 不能将is(类型[]int的变量)用作打印参数中的类型[]接口{}
printarr2(is)
}
// 1、定义一个打印输出数组的函数
func printarr(arr interface{}) { //(arr ()interface{})
//3、上方拨错可以通过类型的断言来实现,x.(T)其实就是判断T是否实现了x接口,如果实现了,就把x接口类型具体化为T类型
for _, i := range arr.([]string) {
fmt.Println(i)
}
}
//泛型 我们不限定它类型,让调用者自己去定义类型 *<> *[]
func printarr2[T string | int](arr []T) {
for _, i := range arr {
fmt.Println(i)
}
}
泛型类型
观察下面这个简单的例子
type s1 []int
var a s1 = []int{1,2,3}//正确
var b s1 = []float32{1.0,2.0,3.0}//X错误,因为IntS1ice的底层类型是[]int,浮点类型的切片无法赋值
这里定义了一个新的类型Intslice,它的底层类型是[]int,理所当然只有int类型的切片能赋值给IntSlice类型的变量。
接下来如果我们想要定义一个可以容纳float32或string等其他类型的切片的话该怎么办?很简单,给每种类型都定义个新类型:
type si []int
type s2 []f1oat32
type s3 []f1oat64
但是这样做的问题显而易见,它们结构都是一样的只是成员类型不同就需要重新定义这么多新类型。那么有没有一个办法能只定义一个类型就能代表上面这所有的类型呢?答案是可以的,这时候就需要用到泛型了:
type slice[T int|float32|float64][]T
不同于一般的类型定义,这里类型名称Slice后带了中括号,对各个部分做一个解说就是:
- T就是上面介绍过的类型形参(Type parameter),在定义Slice类型的时候T代表的具体类型并不确定,类似一个占位符
- int float32|float64这部分被称为类型约束(Type constraint),中间的│的意思是告诉编译器,类型形参T只可以接收 int l或float32或float64这三种类型的实参
- 中括号里的Tintlfloat32/float64这一整串因为定义了所有的类型形参(在这个例子里只有一个类型形参T),所以我们称其为类型形参列表(type parameter list)
- 这里新定义的类型名称叫Slice[T]
这种类型定义的方式中带了类型形参,很明显和普通的类型定义非常不一样,所以我们将这种类型定义中带类型形参的类型,称之为泛型类型
package main
import "fmt"
func main() {
//切片类型 []T T为占位符 [string int]约束为string 和int
type Slice[T string | int | float64] []T
//初始化
var a Slice[int] = []int{1, 2, 3}
fmt.Println(a)
fmt.Printf("Type Name:%T", a)
var b Slice[string]
b = []string{"1, 2, 3"}
fmt.Println(b)
fmt.Printf("Type Name:%T", b)
var c Slice[float64] = []float64{1, 2, 3}
fmt.Println(c)
fmt.Printf("Type Name:%T", c)
//×错误 float32不再类型约束[T string | int | float64]没有floato32
//var d Slice[float32] = []float32{1, 2, 3}
//fmt.Println(d)
//fmt.Printf("Type Name:%T", d)
//×错误 slice[T]是泛型类型,不可直接使用必须实例化为具体的类型
//var a Slice[T] = []int{1, 2, 3}
// 定义一个map泛型
type Mymap[Key string | int, Value float32 | float64] map[Key]Value
var m1 Mymap[string, float64] = map[string]float64{
"Go": 9.0,
"Java": 8.0,
}
fmt.Println(m1)
}
KEY和VALUE是类型形参
int|string 是KEY的类型约束, float32 | float64是VALUE的类型约束
KEY int|string, VALUE float32| float64整个一串文本因为定义了所有形参所以被称为类型形参列表Map[KEY,VALUE]是泛型类型,类型的名字就叫Map[KEY,VALUE]
var a MyMap[string,float64]= xx 中的string和float64是类型实参,用于分别替换KEY和VALUE,实例化出了具体的类型MyMap[string, float64]
泛型函数
这种带类型形参的函数被称为泛型函数
它和普通函数的点不同在于函数名之后带了类型形参。这里的类型形参的意义、写法和用法因为与泛型类型是一模一样的,就不再赘述了.和泛型类型一样,泛型函数也是不能直接调用的,要使用泛型函数的话必须传入类型实参之后才能调用。
Go的泛型(或者或类型形参)目前可使用在3个地方
1、泛型类型-类型定义中带类型形参的类型
2、泛型receiver-泛型类型的receiver
3、泛型函数–带类型形参的函数
package main
import "fmt"
// 泛型函数
type MySlice[T int | float64] []T
// s MySlice[T] 传参s 类型是泛型函数 , Sum()定义Sum方法 ,返回值为T类型
func (s MySlice[T]) Sum() T {
var sum T
for _, v := range s {
sum += v
fmt.Println(s)
}
return sum
}
func main() {
var s MySlice[int] = []int{1, 2, 3, 4}
fmt.Println(s.Sum())
var f MySlice[float64] = []float64{1.0, 2.3, 3.5}
fmt.Println(f.Sum())
fmt.Println(add2(1, 2))
fmt.Println(add2("1", "haha "))
}
// 泛型函数
//
// func add(a int, b int) int {
// return a + b
// }
func add2[T int | float64 | string](a T, b T) T {
return a + b
}
自定义泛型类型
如果类型太多了怎么办呢? 这时候我们就可以自定义泛型类型
package main
import "fmt"
func main() {
fmt.Println(GetNum(1, 2))
}
type AAA interface {
int | int16 | int32 | int64
}
func GetNum[T AAA](a, b T) T {
if a > b {
return a
} else {
return b
}
}
理解泛型三大要素:类型参数、类型集合、类型推断
标签:string,float64,int,fmt,泛型,类型,Go,狂神 From: https://www.cnblogs.com/DuPengBG/p/17027134.html