最近,笔者在刷算法题时有这样的苦恼:算法题中会有很多的排序、比较题型,由于Go是强类型语言,就意味着需要针对不同的类型写出若干相同逻辑的代码,且重复度非常高。
就拿 int、float64 类型的两个数值比较大小来说,有几种写法。
1. 数值比较的几种写法
青铜 – 普通写法
func TwoIntNumMax(a, b int) int{
if a > b {
return a
}
return b
}
func TwoFloat64NumMax(a, b float64) float64{
if a > b {
return a
}
return b
}
可以看出:除了类型不同,函数体中代码逻辑一模一样。然而,这里仅仅才是两种类型。假设,把所有的数值类型都写上一遍,那代码量也是不少。关键是作为有追求的程序员来说,岂能容忍这种做法。
白银 – 使用 interface 的写法
func TwoNumMax(a, b interface{}) (interface{}, error) {
ta := reflect.ValueOf(a)
tb := reflect.ValueOf(b)
switch ta.Kind() {
case reflect.Int:
if tb.Kind() != reflect.Int {
returnnil, ErrType
}
if ta.Int() > tb.Int() {
return a, nil
}
return b, nil
case reflect.Float64:
if tb.Kind() != reflect.Float64 {
returnnil, ErrType
}
if ta.Float() > tb.Float() {
return a, nil
}
return b, nil
default:
returnnil, ErrUnSupport
}
}
func TestMax(t *testing.T) {
r, err := TwoNumMax(1, 2)
if err != nil {
t.Fatal(err)
}
max := r.(int)
fmt.Println("max number is ", max)
}
看似只提供了一个比较方法,但是进到该方法才会发现其中的复杂。如果把其他数值类型都写上的话,那么这个方法会变成一个巨无霸。这里使用 interface 相当于把每一种分开写的方法,糅合在了一个方法里面,结果可想而知。
鉴于以上,在写代码时,笔者感觉很痛苦:究其根源在于写出来的代码量大、重复度高、还不美观。那有没有其他可能?
王者 – 泛型写法
// 泛型类型约束,MinMaxAble 代表 多种类型
type MinMaxAble interface {
// 使用 type 来定义支持的类型
typeint, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64,
float32, float64
}
// MinMaxAble 泛型类型约束
// T 泛型标识, 可以看做是代表了其支持的类型
func max[T MinMaxAble](a, b T) T {
if a > b {
return a
}
return b
}
func TestMax2(t *testing.T) {
// 写法1 使用 [int] 来明确指明 泛型的类型
maxNum := max[int](1, 2)
fmt.Println(maxNum)
// 写法2 不使用 [int] 来明确指明泛型的类型
// 此时 编译器自己会进行泛型类型推断
maxNum2 := max(1, 2)
fmt.Println(maxNum2)
maxNum3 := max[float64](1.1, 2.1)
fmt.Println(maxNum3)
maxNum4 := max(1.1, 2.1)
fmt.Println(maxNum4)
}
似乎发现了了不起的事情!简简单单几行代码,实现了多种数值类型的数值比较,这是什么黑魔法?
对此, 需要了解如下信息:
- 泛型将在 go1.18版本 开始正式支持
- 泛型截止目前(2021/08/20)最新的 go1.17版本 中处于试验阶段
- go1.17版本 中 开启泛型 ,需要添加 gcflags
- go1.17版本 中泛型代码方法或函数不可导出 (只能在当前包中使用)
2. 如何使用泛型
如果想在体验泛型乐趣,需要做如下步骤:
- 升级go版本到 1.17
推荐使用 go 的多版本管理程序 gvm
- 编写 泛型示例代码,编译运行时需要添加 gcflags
- 使用最新版本 Goland IDE ,安装 go1.17 SDK
3. Go泛型示例代码
关于网上go泛型的示例代码,笔者这里推荐两个地方查看。
- go1.17源码中 go1.17/src/go/types/testdata
总结
对于Go来说,泛型的出现是个必然结果。一方面可以简化代码逻辑,另一方面可以在编译时就能确认类型 (interface 在运行时才能确定类型),对于类型不匹配的情况,不至于在运行时才检测出来导致程序崩溃。
还有一点需要各位提前做好准备,等到 go1.18版本 之后,Go的标准库、各种知名库包以及开源程序,一定会基于泛型进行较大的改动。
路漫漫其修远兮吾将上下而求索,笔者相信Go的未来是光明的。