Go进阶之泛型

0 阅读11分钟

1.不使用泛型实现加和函数:

首先实现两个函数对map的value值进行累加.之所以是两个函数.是因为两个map存

储的值类型不同.一个map存储int64类型.另一个map存储float64类型的值.

func main() {
    ints := map[string]int64{
       "1": 1,
       "2": 2,
    }

    floats := map[string]float64{
       "1.1": 1.1,
       "2.2": 2.2,
    }
    fmt.Printf("Non-Generic Sums:%v and %v\n", sumInts(ints), sumFloats(floats))
}

func sumInts(m map[string]int64) int64 {
    var s int64
    for _, v := range m {
       s += v
    }
    return s
}

func sumFloats(m map[string]float64) float64 {
    var s float64
    for _, v := range m {
       s += v
    }
    return s
}

执行结果:

实现同样的功能.因为数据类型不同,就需要为每种类型写不一样的方法.

2.使用泛型实现加和函数:

1).声明泛型函数.就是把函数的参数和返回值"泛化".使其变得通用.通用并不是对所

有类型通用.所以在声明泛型函数时需要声明适用的类型参数.也称作类型约束.Go语

言泛型用一对括号([])来声明泛型适用类型.

func main() {
    ints := map[string]int64{
       "1": 1,
       "2": 2,
    }

    floats := map[string]float64{
       "1.1": 1.1,
       "2.2": 2.2,
    }
    fmt.Printf("Non-Generic Sums:%v and %v\n", SumIntsOrFloats(ints), SumIntsOrFloats(floats))
}


func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
       s += v
    }
    return s
}

SumIntsOrFloats泛型函数通过[K comparable,V int64|float64]声明了两个类

型的参数K和V.供函数参数和返回值使用.

类型参数的K的类型必须为comparable类型.因为它被用作map的key值.而Go语言

要求map的key值必须是可比较类型.

类型参数V的类型可以为int64或float64.在声明使用与或符号(|)来组合所有支持的

类型.

普通参数m使用类型参数来表示一个泛化的map.相应的返回值也是一个泛化的类型.

2).调用泛型函数:

在调用泛型函数的地方.编译器会将泛型函数实例化.即使用真实的类型来替换类型参

数.所以在调用的时候有两种方式.

显示调用:

调用泛型时显示的指明类型参数.

func main() {
    ints := map[string]int64{
       "1": 1,
       "2": 2,
    }

    floats := map[string]float64{
       "1.1": 1.1,
       "2.2": 2.2,
    }
    fmt.Printf("Non-Generic Sums:%v and %v\n", SumIntsOrFloats[string,int64](ints), SumIntsOrFloats[string,float64](floats))
}

隐式调用:

调用泛型函数使用缺省类型参数.让编译器根据实际参数进行推导.

func main() {
    ints := map[string]int64{
       "1": 1,
       "2": 2,
    }

    floats := map[string]float64{
       "1.1": 1.1,
       "2.2": 2.2,
    }
    fmt.Printf("Non-Generic Sums:%v and %v\n", SumIntsOrFloats(ints), SumIntsOrFloats(floats))
}

注:

编译器之所以能够推导出参数类型是因为该函数存在入参.编译器通过传入的变量和

函数的参数声明可以推导出参数类型.但对于没有函数参数的泛型参数来说.编译器无

法进行推导.也就无法实例化泛型参数.此时必须显示的调用并指定类型参数.

3.泛型总览:

1).函数泛化:

为了支持泛型.Go语言函数扩展为可以接受一个使用方括号([])表示的类型参数.

func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
}

同普通参数函数类似.类型参数中的每个参数也有一个类型.比如参数K V的类型分别

为comparable(伴随泛型而引入的内置interface类型,表示可比类型)和

int64|float64(表示int64和float64的集合).

函数中的参数类型是可选的.没有类型参数的函数是传统函数.带有类型参数的函数则

是泛型函数.即便Go1.18引入了泛型并且扩展了函数.仍然保持兼容.

2).类型泛化:

泛型同样扩展了类型的表示方法.它允许在创建自定义类型时也能够接收一个使用方

括号([])表示的类型参数.例如:

//可容纳任意类型的容器
type Vector[T any] []T

Vector声明了一个容器.并使用一个切片来存储元素.元素类型为any(同前面介绍的

comparable一样,也是伴随这泛型而引入的内置interface类型.表示任意类型.等同

于interface{}).所以Vector实际上声明了一个可以容纳任意类型的容器.这种声明中

带有类型参数的类型被称为泛型类型.

泛型类型必须通过类型参数实例化后才可以使用.上述的Vector虽然能够容纳任意类

型.但在使用某个具体类型实例化后.这个容器才能存储该类型的元素.

//实例化为整型容器.
var intVec  Vector[int]
//实例化为字符串容器.
var strVec  Vector[string]

在实例化一个泛型类型时.必须指定类型参数(编译器无法自动推导).其方式与显示调

用泛型函数一致.

泛型类型与普通类型一样.同样允许为其定义方法.但其receiver类型必须带上类型参

数.

func main() {
    ExampleVectorPush()
}

// 可容纳任意类型的容器
type Vector[T any] []T


func (v *Vector[T]) push(x T) {
    *v = append(*v, x)
}

func ExampleVectorPush() {
    var v Vector[string]
    v.push("hello world")
    v.push("hello1 world1")
    fmt.Println(v)
}

为泛型类型定义方法时.必须指定类型参数.但参数名可以与泛型类型的声明不同.并且

如果方法体中并未使用类型参数.甚至可以使用"_"省略.

func (v *Vector[_]) push(x _) {
    *v = append(*v, x)
}

func (v *Vector[Y]) push(x Y) {
	*v = append(*v, x)
}

这两种用法并未带来任何好处.而且在一定程度上降低了可读性.并不推荐使用.在此仅

说明Go语言支持这种写法.

4.类型约束:

1).类型集合:

func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
}

该函数的类型参数中K的类型限定为comparable.V的类型限定为

int64|float64.comparable为interface类型.int64|float64位组合类型.它们都

代表一个类型集合.用于约束泛化的范围.

使用"|"来组合多个类型.从而形成一个类型集合.

2).interface类型集合:

在泛型特性被引入前.interface仅表示一个方法集合.实现了该方法集合的所有类型

都可认为实现了这个interface.在泛型的设计中.对interface进行了扩展.interface

将不在仅仅表示方法的集合.还可以用于表示类型集合.同理.集合内的所有类型都认为

实现了这个interface.


内置interface类型集合:

前面介绍的comparable和any两个interface都是跟随泛型一起被放入的内置in

terface类型.

// 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, 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 interface{ comparable }

compareable表示可比较类型的集合.它仅能用于类型参数中.

any不仅在类型参数中表示任意类型的集合.还可以在非泛型场景中作为interface{}

的别名使用.


自定义interface类型集合:

除了内置的comparable和any两种类型可作为类型的约束使用.用户还可以使用in

terface来定义类型集合.

在泛型之前.interface类型中仅包含方法内或内嵌interface两种元素.引入泛型

后.interface类型将允许使用另外三种元素以表示一个类型集合.

任意类型元素.如int.

近似类型元素.使用表示.如int.

联合类型元素.使用|表示.如~int|~int64.

注:如果interface类型中使用了这三种元素的任意一种.那么这个interface只能用于

泛型类型的参数.


任意类型元素:

任意类型(包括interface类型)都可以出现在一个新的interface类型中.那么这个in

terface只能用于泛型的类型参数.

type integer interface {
    int 
}

此时该interface表示的数据集仅包含一种类型.且仅能用于泛型场景中的类型参数

中.例如.

func SumInteger[T integer](a T,b T) T {
    return a + b
}

类似的.interface类型和自定义类型都可以出现在interface中从而表示一个类型集

合.但是如果定义新的泛型interface.咋不能使用泛型参数.否则编译器检查会失效.


近似类型元素:

在使用interface声明类型集合时.可以使用~来指定一组类型.只要其底层类 ** 型为同一类型即包含在这个集合中.定义一个底层类型为string的集合.

    type AnyString interface {
    ~string
}

这样包含string类型在内.只要其他自定义类型的底层类型为string,就都属于这个集

合.

    func main() {
    var s MyString = "tony"
    SayHi(s)

}

type MyString string

type AnyString interface {
    ~string
}

func SayHi[T AnyString](name T) {
    fmt.Printf("Hello, %s!\n", name)
}

注:波浪号"~"之后的类型必须是某个底层类型.换句话说.如果一个类型的底层类型不

是自身就不能使用"~".比如类型type Mystring string的底层类型不是自身.而是

string.如果使用Mystring定义近似类型集合.则会出现编译错误. interface也不能

用于定义近似类型集合.因为interface的底层类型并不确定.并且肯定不是自身.


联合类型元素:

前面使用"~"定义的元素集合仅能包括一组底层类一致的类型.有时我们需要联合多

种类型.甚至联合多种底层类型一致的类型.此时可以使用"|"符号定义一个更宽泛的类

型集合.

    type PredeclaredSignedInteger interface {
    int | int8 | int16 | int32 | int64
}

上面类型表示的类型集合将包含五种预制类型.

希望把所有底层为有符号整型的类型组合在一起.则可以结合"~"来使用.

    type PredeclaredSignedInteger interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}

interface类型集合运算:

前面使用interface声明集合类型时.我们均使用了一行代码指定一个集合.(可理解为

子集).事实上interface支持按行指定多个子集合.这些子集合取交集形式形成最终的

集合.

    type MyString interface {
    ~string
    string
}

MyString的类型集合由两个子集组成.一个是所有底层类型为string的集合.另一个

是单一类型.两个子集取交集.最终的类型集合只包含string一个类型.


基于操作的类型集合:

有时我们根据类型支持的操作符定义类型集合.比如我们希望定义一个泛型函数来比

较两个元素的大小.

    func GreaterThan[T Ordered](a, b T) bool {
    if a > b {
       return true
    }
    return false
}

type Ordered interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
       ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
       ~float32 | ~float64 |
       ~string
}

4.泛型举例:

1).泛型示例MapKeys:

    func MapKeys[K comparable, V any](m map[K]V) []K {
    r := make([]K, len(m))
    for k := range m {
       r = append(r, k)
    }
    return r
}

类型参数K被用于声明了一个泛型的切片.然后遍历把遍历到的键值逐个添加到切片中

并返回.

2).泛型示例Set:

Set容器可以存储一组不重复的数据.广泛用于需要去重的场景.

    // set底层用map实现.
type Set[T comparable] map[T]struct{}

// 构建某个(comparable)类型的set.
func MakeSet[T comparable]() Set[T] {
    return make(Set[T])
}

// Add.添加元素.
func (s Set[T]) Add(x T) {
    s[x] = struct{}{}
}

// delete.删除元素.
func (s Set[T]) Remove(x T) {
    delete(s, x)
}

// 查询元素v是否已存在.
func (s Set[T]) Contains(x T) bool {
    _, ok := s[x]
    return ok
}

// 返回元素个数.
func (s Set[T]) Len() int {
    return len(s)
}

// 遍历set.并逐个调用函数f.
func (s Set[T]) Iterate(f func(T)) {
    for v := range s {
       f(v)
    }
}

3).泛型示例Sort:

如果要对元素切片中的元素排序.标准库中提供了sort.slice之前.每种类型的切片都

需要实现sort.Interface接口中的三个方法.然后使用sort.Sort方法进行排序.即便

后来标准库引入了sort.Slice.使用时仍然需要提供一个排序函数.

    func main() {
    s := []int{3, 5, 2}
    OrderedSlice(s)
    fmt.Println(s)
}

type Ordered interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
       ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
       ~float32 | ~float64 | ~string
}

type orderedSlice[T Ordered] []T

func (s orderedSlice[T]) Len() int {
    return len(s)
}

func (s orderedSlice[T]) Less(i, j int) bool {
    return s[i] < s[j]
}

func (s orderedSlice[T]) Swap(i, j int) {
    s[i], s[j] = s[j], s[i]
}

func OrderedSlice[T Ordered](slice []T) {
    //slice先转换为orderedSlice然后排序.因此不必在实现sort.Interface接口.
    sort.Sort(orderedSlice[T](slice))
}

执行结果:

语雀地址www.yuque.com/itbosunmian…?

《Go.》 密码:xbkk 欢迎大家访问.提意见.

渐渐的风干.



如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路