wayou / wayou.github.io

https://wayou.github.io/
147 stars 12 forks source link

Golang 泛型 #314

Open wayou opened 2 years ago

wayou commented 2 years ago

Golang 泛型

Golang 1.18+ 中加入了泛型,果断升级后体验了一波。

考察数组求和的场景,为了对比,先来看不使用泛型时如何实现。

不使用泛型的数组求和

分别编写两个求和函数,对整形和浮点型进行求和。

func sumInt(a []int64) int64 {
    var sum int64
    for _, v := range a {
        sum += v
    }
    return sum
}

func sumFloat(a []float64) float64 {
    var sum float64
    for _, v := range a {
        sum += v
    }
    return sum
}

然后在 main 函数中进行调用:

func main() {
    ints := []int64{1, 2, 3, 4, 5}
    floats := []float64{1.1, 2.2, 3.3, 4.4, 5.5}

    fmt.Printf("%v\n", sumInt(ints)) // 15
    fmt.Printf("%v\n", sumFloat(floats)) // 16.5
}

加入泛型

上面没使用泛型的版本,针对不同类型的入参,需要编写单独的函数。泛型的引入便可解决该问题,使得函数可接收不同类型入参,在编译后进行类型替换。

以下是带泛型的求和函数:

func sum[T int64 | float64](a []T) T {
    var sum T
    for _, v := range a {
        sum += v
    }
    return sum
}

其中函数名后面方括号中定义了类型 T,联合类型 int64 | float64 为一种入参约束(parameter constraint) 定义了函数可接收的参数类型,后续返回和函数体中可直接引用这里的 T 类型。

然后在 main 函数中使用这个新定义的函数:

fmt.Printf("%v\n", sum[int64](ints)) // 指定类型的调用
fmt.Printf("%v\n", sum(ints)) // 省略类型的调用

使用时也是通过在函数名后面的方括号中指定具体的类型,当然也可以省略。当类型省略时,编译工具根据入参进行自动检测。所以并不是所有情况都能省略,即,函数本身不带入参的情况。

类型约束

进一步可将上面的联合类型 int64 | float64 抽取到一个单独的类型中,方便复用,而不是每次都完整地书写一遍。

type Number interface {
    int64 | float64
}

使用上面新定义的类型约束更新我们的泛型函数:

-   func sum[T int64 | float64](a []T) T {
+   func sum[T Number](a []T) T {
       var sum T
        for _, v := range a {
            sum += v
        }
        return sum
    }

多个入参类型

函数的泛型入参并不局限于单个,可以是任意个。考察下面对一个 map 进行求和的情况:

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

上面一共声明了两个入参类型 KV。其中 comparable 为 Golang 内置的类型,表示任意能进行对比(即运用 ==!= 操作符)的类型都可作为合法输入,这里 K 作为 map 的键值。

上述函数的调用:

numberMap := map[int64]float64{1: 1.1, 2: 2.2, 3: 3.3, 4: 4.4, 5: 5.5}
fmt.Printf("%v\n", sumMap(numberMap)) // 16.5

相关资源