go1.18泛型全部教程
go1.18泛型全部教程
一 什么是泛型
泛型的英文是Generics,就是函数的参数,或者容器元素的类型,支持更广泛的类型,不再是特定的类型。
下面只搬运一下对泛型的简单介绍
- 函数和类型声明的语法接受类型参数
- 可以通过方括号中的类型参数列表来实例化参数化函数和类型
- 接口类型的语法现在允许嵌入任意类型以及Union和〜T类型元素。这些接口只能用作类型约束。接口现在可以定义一组类型和一组方法
- 新的预声明标识符any是空接口的别名,可以使用any代替interface{}
- 新的预声明标识符comparable表示可以使用==或!=做比较的所有类型的一个接口,它可以被用作类型约束
- 有三个实验性的package在使用泛型.golang.org/x/exp下的所有package都属于试验性质或者被废除的package,不倡议应用。
- golang.org/x/exp/constraints
- golang.org/x/exp/slices
- golang.org/x/exp/maps
在Golang、Java、C++等这类静态语言中,是需要严格定义传入变量的类型的,斌不是随心所欲,例如在golang中:
func Sum(a, b int) int {
return a + b
}
在函数Sum中,不仅要严格定义传入参数a和b的变量类型,而且返回值的类型也需要严格定义,所有你只能传入int类型进行调用:
Sum(1, 2) // 3
如果传入其它类型的变量就会报错:
fmt.Println(Sum(1.23, 2.54));
./main.go:33:18: cannot use 1.23 (untyped float constant) as int value in argument to Sum (truncated)
./main.go:33:24: cannot use 2.54 (untyped float constant) as int value in argument to Sum (truncated)
因此,如果当golang开发者想开发类似实现两个float类型变量相加的功能,只能另写一个函数:
func SumFloat(a, b float) float {
return a + b
}
或者写一个通用的Sum函数使用interface反射来判断:
func Sum(a, b interface{}) interface{} {
switch a.(type) {
case int:
a1 := a.(int)
b1 := b.(int)
return a1 + b1
case float64:
a1 := a.(float64)
b1 := b.(float64)
return a1 + b1
default:
return nil
}
}
这样的话,不仅重复很多代码,而且类型频繁转换导致不仅性能低效,安全性上也不高。
所以泛型诞生了。
然而泛型是一把双刃剑,在给开发者带来便利的同时,同样会带来编译和效率的问题,因为泛型需要系统去推倒和计算变量的类型的,这在无形中会增加编译的时间和降低运行效率。
二 Golang中的泛型
首先来看一下,在Golang 1.18版本中是如何利用泛型来实现Sum函数的
func Sum[T int|float64](a,b T) T {
return a + b
}
然后再调用一下:
fmt.Println(Sum[int](1, 2)) //3
fmt.Println(Sum[float64](1.23, 2.54)) //3.77
先不去理解函数中各组件的含义,仅仅看代码就简洁了不少,函数也实现了多个类型的功能。
三 泛型语法详解
3.1 泛型的语法
MyType[T1 constraint1 | constraint2, T2 constraint3...] ...
泛型的语法非常简单, 就类似于上面这样, 其中:
MyType可以是函数名, 结构体名, 类型名…
T1, T2…是泛型名, 可以随便取
constraint的意思是约束, 也是泛型中最重要的概念, 接下来会详解constraint
使用 | 可以分隔多个constraint, T满足其中之一即可(如T1可以是constraint1和constraint2中的任何一个)
3.2 Constraint(约束)是什么
约束的意思是限定范围, constraint的作用就是限定范围, 将T限定在某种范围内
而常用的范围, 我们自然会想到的有:
any(interface{}, 任何类型都能接收, 多方便啊!)
Interger(所有int, 多方便啊, int64 int32…一网打尽)
Float(同上)
comparable(所有可以比较的类型, 我们可以给所有可以比较的类型定制一些方法)
…
这些约束, 不是被官方定义为内置类型, 就是被涵盖在了constraints包内!!!
下面是builtin.go的部分官方源码:
// any is an alias for interface{} and is equivalent to interface{} in all ways.
type any = interface{}
// comparable is an interface that is implemented by all comparable types
// (booleans, numbers, strings, pointers, channels, interfaces,
// arrays of comparable types, structs whose fields are all comparable types).
// The comparable interface may only be used as a type parameter constraint,
// not as the type of a variable.
type comparable comparable
下面是constraints.go的部分官方源码:
// Integer is a constraint that permits any integer type.
// If future releases of Go add new predeclared integer types,
// this constraint will be modified to include them.
type Integer interface {
Signed | Unsigned
}
// Float is a constraint that permits any floating-point type.
// If future releases of Go add new predeclared floating-point types,
// this constraint will be modified to include them.
type Float interface {
~float32 | ~float64
}
//......
3.3 自定义constraint(约束)
下面是constraints包中的官方源码:
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
Signed约束就是这样被写出来的, 其中需要我们掌握的点有如下几个:
使用interface{}就可以自定义约束
使用 | 就可以在该约束中包含不同的类型, 例如int, int8, int64均满足Signed约束
你可能会有疑问, ~是什么??? int我认识, ~int我可不认识呀??? 没关系, 实际上~非常简单, 它的意思就是模糊匹配, 例如:
type MyInt int64
此时 MyInt并不等同于int64类型(Go语言特性)
若我们使用int64来约束MyInt, 则Myint不满足该约束
若我们使用~int64来约束MyInt, 则Myint满足该约束(也就是说, ~int64只要求该类型的底层是int64, 也就是模糊匹配了)
官方为了鲁棒性, 自然把所有的类型前面都加上了~
例如:
type My_constraint_Num interface {
~int64 | ~float64
}
1. 声明一个泛型函数
package main
import "fmt"
func printSlice[T int | int64 | float64 | string](data []T) {
for _, v := range data {
fmt.Println(v)
}
}
func printSliceAny[T any](data []T) {
for _, v := range data {
fmt.Println(v)
}
}
//多个泛型参数语法:
func printSliceDemo01[T, M any](data01 []T, data02 []M) {
fmt.Println("printSliceDemo01================")
fmt.Println(data01)
fmt.Println("printSliceDemo01================")
fmt.Println(data02)
}
// []里写一个类型,传入的data01和data02 必须是同一种数据类型
func printSliceDemo02[T any](data01 []T, data02 []T) {
fmt.Println("printSliceDemo02================")
fmt.Println(data01)
fmt.Println("printSliceDemo02================")
fmt.Println(data02)
}
// []里写一个类型,传入的data01和data02 必须是同一种数据类型
func printSliceDemo03[T any](data01, data02 []T) {
fmt.Println("printSliceDemo03================")
fmt.Println(data01)
fmt.Println("printSliceDemo03================")
fmt.Println(data02)
}
func printSliceDemo04[T any, M any](data01 []T, data02 []M) {
fmt.Println("printSliceDemo04================")
fmt.Println(data01)
fmt.Println("printSliceDemo04================")
fmt.Println(data02)
}
func main() {
// 显示类型调用
printSlice[int]([]int{66, 77, 88, 99, 100})
printSlice[float64]([]float64{1.1, 2.2, 5.5})
printSlice[string]([]string{"烤鸡", "烤鸭", "烤鱼", "烤面筋"})
// 省略显示类型调用
printSlice([]int64{55, 44, 33, 22, 11})
printSliceAny([]int64{55, 44, 33, 22, 11})
printSliceDemo01([]int64{55, 44, 33, 22, 11}, []string{"烤鸡", "烤鸭", "烤鱼", "烤面筋"})
printSliceDemo02([]int64{55, 44, 33, 22, 11}, []int64{55, 44, 33, 22, 11})
printSliceDemo03([]int64{55, 44, 33, 22, 11}, []int64{55, 44, 33, 22, 11})
printSliceDemo04([]int64{55, 44, 33, 22, 11}, []string{"烤鸡", "烤鸭", "烤鱼", "烤面筋"})
}
[T any]参数的类型,意思是该函数支持任何T类型; 底层是 type any = interface{}
多个泛型参数语法:
[T, M any]
[T any, M any]
[T any, M comparable]
在调用这个泛型函数的时候
可以显示指定类型参数
如: printSlice[int]([]int{66, 77, 88, 99, 100})
也可以省略显示类型 自动推断类型
如 printSlice([]int64{55, 44, 33, 22, 11})
2. 声明一个泛型切片
带有类型参数的类型被叫做泛型类型。下面定义一个底层类型为切片类型的新类型 vector。它是可以存储任何类型的的切片。要使用泛型类型,要先对其进行实例化,就是给类型参数指定一个实参。
package main
import (
"fmt"
"sort"
"strings"
"golang.org/x/exp/constraints"
)
type vector[T any] []T
func printSlice[T any](data []T) {
fmt.Println(data)
}
func main() {
//dome01()
//sortSliceDome()
//ContainsSliceDome()
findFuncDemo()
//filterSliceDome()
//Contains[comparable]([int]{58, 1881},58)
//testMinMax()
}
func dome01() {
v := vector[int]{58, 1881}
printSlice(v)
v2 := vector[string]{"烤鸡", "烤鸭", "烤鱼", "烤面筋"}
printSlice(v2)
v3 := vector[float64]{10.2, 2.5}
printSlice(v3)
var v4 vector[int] = []int{1, 2, 3}
v4[2] = 4
printSlice(v4)
}
func sortSliceDome() {
floatSlice := []float64{2.3, 1.2, 0.2, 51.2}
sortSlice(floatSlice, "asc")
fmt.Println(floatSlice)
stringSlice := []string{"z", "a", "b"}
sortSlice(stringSlice, "asc")
fmt.Println(stringSlice)
intSlice := []int{0, 3, 2, 1, 6}
sortSlice(intSlice, "desc")
fmt.Println(intSlice)
}
// 切片排序 order asc|desc
func sortSlice[T constraints.Ordered](s []T, order string) {
if strings.ToUpper(order) == "ASC" || order == "" {
sort.Slice(s, func(i, j int) bool {
return s[i] < s[j]
})
} else {
sort.Slice(s, func(i, j int) bool {
return s[i] > s[j]
})
}
}
func ContainsSliceDome() {
floatSlice := []float64{2.3, 1.2, 0.2, 51.2}
fmt.Println(ContainsSlice(floatSlice, 2.3))
stringSlice := []string{"z", "a", "b"}
fmt.Println(ContainsSlice(stringSlice, "c"))
intSlice := []int{0, 3, 2, 1, 6}
fmt.Println(ContainsSlice(intSlice, 0))
}
// ContainsSlice 是否包涵
func ContainsSlice[E constraints.Ordered](s []E, v E) bool {
for _, vs := range s {
if v == vs {
return true
}
}
return false
}
func findFuncDemo() {
fmt.Println(FindFunc([]int{1, 2, 3, 4, 5, 6}, 2)) //1
}
//FindFunc 查找元素
//该方法应用于在已知切片中查找给定元素是否存在,若存在则返回元素所在切片下标,不存在则返回 -1。
//支持泛型类型:comparable。即属于相同泛型类型的不同元素之间必须可以比较是否相等。
func FindFunc[T comparable](a []T, v T) int {
for i, e := range a {
if e == v {
return i
}
}
return -1
}
func filterSliceDome() {
websites := []string{"http://foo.com", "https://bar.com", "https://gosamples.dev"}
httpsWebsites := FilterSlice(websites, func(v string) bool {
return !strings.HasPrefix(v, "https://")
})
fmt.Println(httpsWebsites)
httpsWebsites2 := FilterSlice(websites, func(v string) bool {
return strings.HasPrefix(v, "https://")
})
fmt.Println(httpsWebsites2)
numbers := []int{1, 2, 3, 4, 5, 6}
divisibleBy2 := FilterSlice(numbers, func(v int) bool {
return v%2 == 0
})
fmt.Println(divisibleBy2)
//输出:
//[https://bar.com https://gosamples.dev]
//[2 4 6]
}
//FilterSlice 过滤出符合传入方法的数据
func FilterSlice[T any](slice []T, f func(T) bool) []T {
var n []T
for _, e := range slice {
if f(e) {
n = append(n, e)
}
}
return n
}
func testMinMax() {
vi := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
result := Max(vi)
fmt.Println(result)
vi = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
result = Min(vi)
fmt.Println(result)
//输出
//10
//1
}
type minmax interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64
}
func Max[T minmax](a []T) T {
m := a[0]
for _, v := range a {
if m < v {
m = v
}
}
return m
}
func Min[T minmax](a []T) T {
m := a[0]
for _, v := range a {
if m > v {
m = v
}
}
return m
}
golang.org/x/exp 包
文档地址:
https://pkg.go.dev/golang.org/x/exp
注意:
不建议使用这个包:
详细参考下面文档:
关于golang:重磅Go-118将移除用于泛型的constraints包
https://lequ7.com/guan-yu-golang-zhong-bang-go118-jiang-yi-chu-yong-yu-fan-xing-de-constraints-bao.html
golang.org/x下所有package的源码独立于Go源码的骨干分支,也不在Go的二进制安装包里。如果须要应用golang.org/x下的package,能够应用go get来装置。
golang.org/x/exp下的所有package都属于试验性质或者被废除的package,不倡议应用。
因为泛型的存在,相同的功能对于不同类型的slice可以少写一份代码,如果想使用slice泛型的相关操作,建议复制golang.org/x/exp中的函数进行使用或修改
constraints包里定义了Signed,Unsigned, Integer, Float, Complex和Ordered共6个interface类型,能够用于泛型里的类型束缚(type constraint)。
官方也引入了一些官方包来方面泛型的使用,具体如下:
// constraints 定义了一组与类型参数一起使用的约束
package constraints
// Signed是允许任何有符号整数类型的约束。
type Signed interface { ... }
// Unsigned是允许任何无符号整数类型的约束。
type Unsigned interface { ... }
// Integer是允许任何整数类型的约束。
type Integer interface { ... }
// Float是一个允许任何浮点类型的约束。
type Float interface { ... }
// Complex是允许任何复杂数值类型的约束。
type Complex interface { ... }
// Ordered是一个约束,允许任何有序类型:任何支持操作符< <= >= >的类型。
type Ordered interface { ... }
使用方式示例如下:
package main
import (
"fmt"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
"sort"
)
func main() {
EqualDome()
//ContainsDome()
//ContainsFuncDome()
//InsertDome()
}
// EqualDome 是否等于
func EqualDome() {
var m1 = map[int]int{1: 2, 2: 4, 4: 8, 8: 16}
wantKeys := []int{1, 2, 4, 8}
gotKeys := maps.Keys(m1)
sort.Ints(gotKeys)
// gotKeys 是否等于 wantKeys
fmt.Println(slices.Equal(gotKeys, wantKeys))
var m2 = map[int]string{1: "a", 2: "b", 4: "c", 8: "d"}
wantValsAsc := []string{"a", "b", "c", "d"}
wantValsDesc := []string{"d", "c", "b", "a"}
gotVals := maps.Values(m2)
sort.Strings(gotVals) // 升序
// gotKeys 是否等于 wantKeys
fmt.Println(slices.Equal(gotVals, wantValsAsc))
sort.Sort(sort.Reverse(sort.StringSlice(gotVals))) //降序
// gotKeys 是否等于 wantValsDesc
fmt.Println(slices.Equal(gotVals, wantValsDesc))
// 打印结果
//true
//true
//true
}
func ContainsDome() {
floatSlice := []float64{2.3, 1.2, 0.2, 51.2}
// floatSlice 是否包涵 2.3
fmt.Println(slices.Contains(floatSlice, 2.3))
stringSlice := []string{"z", "a", "b"}
fmt.Println(slices.Contains(stringSlice, "c"))
intSlice := []int{0, 3, 2, 1, 6}
fmt.Println(slices.Contains(intSlice, 0))
// 打印结果
//true
//false
//true
}
func ContainsFuncDome() {
floatSlice := []float64{2.3, 1.2, 0.2, 51.2}
// floatSlice 是否包涵 > 1.0
fmt.Println(slices.ContainsFunc(floatSlice, func(v float64) bool {
return v > 1.0
}))
stringSlice := []string{"z", "a", "b"}
// stringSlice 是否包涵 == "c"
fmt.Println(slices.ContainsFunc(stringSlice, func(v string) bool {
return v == "c"
}))
intSlice := []int{0, 3, 2, 1, 6}
// intSlice 是否包涵 v%2 == 0
fmt.Println(slices.ContainsFunc(intSlice, func(v int) bool {
return v%2 == 0
}))
// 打印结果
//true
//false
//true
}
func InsertDome() {
i := []int{1, 2, 3}
// i中第1个角标位置插入4 5
gotInt := slices.Insert(i, 1, []int{4, 5}...)
fmt.Println(gotInt)
// f中第1个角标位置插入4.1, 5.6
f := []float64{1.2, 2.2, 3.3}
gotFloat64 := slices.Insert(f, 1, []float64{4.1, 5.6}...)
fmt.Println(gotFloat64)
s := []string{"a", "b", "c"}
// s中第1个角标位置插入"e", "f"
gotString := slices.Insert(s, 1, []string{"e", "f"}...)
fmt.Println(gotString)
// 打印结果
//[1 4 5 2 3]
//[1.2 4.1 5.6 2.2 3.3]
//[a e f b c]
}
更多介绍:
Go1.18新特性--泛型
https://www.cnblogs.com/aganippe/p/16014701.html
3. 声明一个泛型map
package main
import "fmt"
func main() {
testDemo01()
}
func testDemo01() {
type M[K string, V any] map[K]V //这里的K不支持any ,由于底层map不支持,所以使用string
m1 := M[string, int]{"key": 1}
m1["key"] = 2
m2 := M[string, string]{"key": "value"}
m2["key"] = "new value"
fmt.Println(m1, m2)
//打印
//map[key:2] map[key:new value]
}
4. 声明一个泛型通道
package main
import "fmt"
type C[T any] chan T
func main() {
c1 := make(C[int], 10)
c1 <- 1
c1 <- 2
c2 := make(C[string], 10)
c2 <- "hello"
c2 <- "world"
fmt.Println(<-c1, <-c2)
//打印
//1 hello
}
5. 泛型约束
5.1 使用interface中规定的类型约束泛型函数的参数
NumStr,新增了类型列表表达式,它是对类型参数进行约束。
使用 | 表示取并集
如果传入参数不在集合限制范围内,就会报错。
package main
import "fmt"
type NumStr interface {
Num | Str
}
type Num interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~complex64 | ~complex128
}
type Str interface {
~string
}
func add[T NumStr](a, b T) T {
return a + b
}
//使用interface中规定的类型约束泛型函数的参数
func main() {
fmt.Println(add(3, 4))
fmt.Println(add("hello", "world"))
//打印
//7
//helloworld
}
5.2 使用interface中规定的方法来约束泛型的参数
package main
import (
"fmt"
"strconv"
)
type ShowPrice interface {
String() string
}
type Price int
func (i Price) String() string {
return strconv.Itoa(int(i))
}
type Price2 string
func (i Price2) String() string {
return string(i)
}
func ShowPriceList[T ShowPrice](s []T) (ret []string) {
for _, v := range s {
ret = append(ret, v.String())
}
return
}
//使用interface中规定的方法来约束泛型的参数
func main() {
fmt.Printf("%T %+v \n", ShowPriceList([]Price{1, 2}), ShowPriceList([]Price{1, 2}))
fmt.Printf("%T %+v \n", ShowPriceList([]Price2{"a", "b"}), ShowPriceList([]Price2{"a", "b"}))
//打印
//[]string [1 2]
//[]string [a b]
}
5.3 使用interface中规定的方法和类型来双重约束泛型的参数
package main
import (
"fmt"
"strconv"
)
type ShowPrice interface {
String() string
int | string
}
type Price int
func (i Price) String() string {
return strconv.Itoa(int(i))
}
func ShowPriceList[T ShowPrice](s []T) (ret []string) {
for _, v := range s {
ret = append(ret, v.String())
}
return
}
//使用interface中规定的方法和类型来双重约束泛型的参数
func main() {
fmt.Printf("%T %+v", ShowPriceList([]Price{1, 2}), ShowPriceList([]Price{1, 2}))
}
//传入浮点参数,就会因为不是约束类型而报错
// .\main.go:27:36: Price does not implement ShowPrice (possibly missing ~ for int in constraint ShowPrice)
5.4 使用泛型自带comparable约束,判断比较
package main
import (
"fmt"
)
func findFunc[T comparable](a []T, v T) int {
for i, e := range a {
if e == v {
return i
}
}
return -1
}
func main() {
fmt.Println(findFunc([]int{1, 2, 3, 4, 5, 6}, 5))
fmt.Println(findFunc([]string{"烤鸡", "烤鸭", "烤鱼", "烤面筋"}, "烤面筋"))
// 打印
// 4
// 3
}
comparable 的约束类型支持整数 和字符,自定义结构体,也可以嵌套在自定义约束中
type ShowPrice interface {
int | string | comparable
}
6.声明一个泛型struct
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
type Vector[T constraints.Ordered] struct {
x, y T
}
func (v *Vector[T]) Add(x, y T) {
v.x += x
v.y += y
}
func (v *Vector[T]) String() string {
return fmt.Sprintf("{x: %v, y: %v}", v.x, v.y)
}
func NewVector[T constraints.Ordered](x, y T) *Vector[T] {
return &Vector[T]{x: x, y: y}
}
func main() {
v := NewVector[float64](1, 2)
v.Add(2, 3)
fmt.Println(v)
v2 := NewVector[string]("a", "b")
v2.Add("1", "2")
fmt.Println(v2)
//打印:
//{x: 3, y: 5}
//{x: a1, y: b2}
}
参考文档:
http://www.golang.ren/article/193584
https://blog.csdn.net/QcloudCommunity/article/details/121219750
https://cdn.modb.pro/db/528594
视频教程:
https://www.bilibili.com/video/BV1PY41137Vn?p=2&vd_source=a68414cd60fe26e829ce1cdd4d75a9e6