个人自用的Go泛型的简明指南

780 阅读3分钟

请在golang 1.18后再使用泛型

什么场合需要泛型呢?

我自己的回答:其实大部分场合我们都不太需要请泛型这尊大佛。golang的接口可以应对我们的需求。我们在1.18之前,没有泛型,还不是一样的写代码。但是有了泛型之后,写一些工具类代码更清晰。golang的反射用起来还是挺麻烦的。我想,我们平时需要做各种验证吧,比如,数值在XX之间,检查数组中的数值合不合要求,这些当然用传统的go代码也可以,但是不如泛型明了。 来个超级简单的前瞻:

// 定义
func Sum[T int | float64](a, b T) T {
    return a + b
}
// 调用
Sum(1, 2)
Sum(1.0, 2.0)
Sum[int](1, 2) // 纯纯的多余,因为编译器可以算出来

也许你会有以下疑问?

  1. 可以在结构体方法中用么?
  2. 类型参数可以是结构体,可以是interface么?
  3. 调用的参数类型可以多种混合么?类似Sum(1,2.0)

我们先看一点小知识,后续再解答这些问题吧。

前置小知识

  1. interface{}可以简写为any
  2. comparable是指可比较类型,但是又不能比较,因为只支持 ==、!=
  3. 类型参数:[TypeName1 类型1 TypeName2 类型2] 这里值得好好注意
    FBI WARNING
    类型参数可以是只是一组类型参数 如:[T int|string][Nxxx string|uint]等,第一个TypeName没有什么命名要求。第二个类型就有点讲究了。可以复合type参数,如 [T int|string], 还可以是类型混结构体 [T MyStruct|int], 还可以玩的很极限,没有数量限制 [T uint | uint8 | uint16 | string | bool]。但是,请不要和interface混用,不是我不让,也没有什么人生经验,纯是编译器不让我们玩的这么极限。 你也可以玩的很极限,来多组类型。

实践一下

说了这么多,上货!我们先从简单的情况开始说起,然后慢慢增加使用场合。

  1. 普通函数的泛型
  2. 结构体函数的泛型

1 普通函数

普通函数的代码片段就和上文一样,但是我还是把他贴到下面了

// 定义
func Sum[T int | float64](a, b T) T {
    return a + b
}

然后就是调用

Sum(1, 2)
Sum(1.0, 2.0)
Sum[int](1, 2)
Sum[float64](1.0, 2.0)

是可以不用写两组Sum分别计算int及float64了,那么问题来了,我要计算int + float64呢?就像这样Sum(1,1.0),结果肯定是不行的。 所以Sum(1.0,2)也是不行的。泛型似乎有用,但是没有那么有用。 参数类型也可以是多组,但是多组之间也不能互相直接参与计算。因为直接报错了,所以直接上图 多个参数类型直接报错图

其实也是很蛋疼的事情。那么多组还有什么用呢?有的,看官方代码,计算一个map的总和,map有两个类型么,就派上用场了。

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

也许看到这里,你会大失所望吧,就这就这?是啊,其实泛型只适合写一点通用的代码,链表啊、图啊、比比大小啊、计算总和啊,没有我们想的那么灵活。

2 结构体函数

写一个验证器,验证大小对不对

package val
type number interface {
    ~int | ~float64
}
type MinVaildate[T number] struct {
    min T
}
func (m MinVaildate[T]) DoVaildate(value T) bool {
    return m.min < value
}

有小伙伴就要喷了,啊,就判断个最小值,柠有必要搞这么复杂么? 我目前找不到什么好的例子了。而且,已经出现bug了。

图片.png

先这样吧,后续再更