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

作者:admin  创建时间:2022-12-24 19:38
最后编辑:admin  更新时间:2024-12-22 19:32