go1.18泛型初体检

在 Go v1.18 中,Go 语言新增三个功能,分别是“泛型”、“模糊测试” 和 “工作区”。

本文我们介绍 Go 社区呼声最高的 “泛型” 的使用方式。

02 

引言

读者朋友们应该了解 Go 语言是一门强类型语言,如果你是从弱类型语言转过来的话,刚开始上手时可能会比较别扭。

比如,我列举一个简单示例:

func MinInt(x, y int) int {
    if x < y {
        return x
    }
    return y
}

阅读上面这段代码,我们定义一个比较大小的函数 MinInt(),需要注意的是,该函数参数列表和返回值的类型是 int,如果我们想要比较的数值是 float64 浮点数类型,我们就无法使用该函数。

聪明的读者朋友们可能会想到,再定义一个函数 MinFloat64() 函数。

func MinFloat64(x, y float64) float64 {
    if x < y {
        return x
    }
    return y
}

阅读上面这段代码,我们又定义一个比较 float64 类型的数值大小的函数,这种办法也不是不可以,但是,我们知道 Go 语言的数值类型还有其他很多种,比如 int8int32int64uint8 ...

所以,我们难道需要为每种数值类型都定义一个比较大小的函数吗?

此时,经验丰富的读者朋友们想到了使用 interface{} 空接口类型,代码如下:

func MinAny(x, y interface{}) interface{} {
 if x, ok := x.(int); ok {
  if y, ok := y.(int); ok {
   if x < y {
    return x
   }
  }
 }
 if x, ok := x.(float64); ok {
  if y, ok := y.(float64); ok {
   if x < y {
    return x
   }
  }
 }
 return y
}

阅读上面这段代码,我们将函数的参数列表和返回值都定义为空接口类型 interface{},使用此方法确实可以避免我们为每种数值类型定义一个比较数值大小的函数,但是,该方式也有弊端,那就是需要我们在函数体中,对每种数值类型做类型断言。

综上所述,在 Go v1.18 之前,我们如果想要使用相同的逻辑处理不同类型的变量时,就会比较麻烦。

Go v1.18 新增的“泛型”功能,使这个问题得到解决,避免我们写一些重复代码。

03 

类型参数

Go 语言中的“泛型”是通过支持类型参数实现的,类型参数又可以分为“函数的类型参数”,“类型的类型参数”和“方法的类型参数”。

函数的类型参数 - 泛型函数

我们先使用 “泛型” 重写一下 Part 02 的 MinAny() 函数,代码如下:

func MinAny[T int](x, y T) T {
    if x < y {
        return x
    }
    return y
}

阅读上面这段代码,我们可以发现,在函数名和参数列表之间的 [T int],这就是类型参数,我们在函数 MinAny 中使用类型参数,该函数就是“泛型函数”。

类型参数支持多个类型,使用 | 分隔,例如:[T int | float64]

想必有读者朋友们会问,如果类型参数需要支持所有数值类型,那岂不是[]中的代码会特别长。

不用担心,我们可以声明一个接口类型,不同的是,接口类型中不再是函数,而是类型,代码如下:

type ordered interface {
    ~int | ~float64
}

阅读上面这段代码,我们定义一个类型 ordered,他的语法类似定义接口类型,区别是函数变成了类型名称(波浪线开头代表类型本身和以该类型为底层类型的所有类型)。

我们改写一下 MinAny 函数,代码如下:

func MinAny[T ordered](x, y T) T {
    if x < y {
        return x
    }
    return y
}

需要注意的是,如果 [] 中包含多个类型参数,需要使用英文逗号 , 分隔,并且类型参数的形参名字不能相同,例如:[T ordered, T1 ordered1]

类型的类型参数 - 泛型类型

与函数一样,Go 语言的自定义类型也可以使用类型参数,例如:

type MinSalary[T int | float64] struct {
    salary T
}

阅读上面这段代码,我们定义一个自定义类型 MinSalary,它是一个“泛型类型”,与定义一个自定义“普通类型”的区别是在类型名字后面跟一个[]中括号,里面包含类型参数(其中T是类型形参,int 和 float64是类型实参)。

需要注意的是,“泛型类型”和“泛型函数”使用方式不同,它不能像“泛型函数”具备类型推断的功能,而是需要显示指定类型实参,代码如下:

salary := &MinSalary[int]{
    salary: 1000,
}
fmt.Printf("%+v\n", salary)

方法的类型参数 - 泛型方法

我们知道,Go 语言可以为自定义类型定义方法,同样也可以为“泛型类型”定义“泛型方法”。

我们还是先来阅读一段代码:

type Salary[T int | float64] struct {
    money T
}

func (s *Salary[T]) Min(x, y T) T {
    if x < y {
        return x
    }
    return y
}

阅读上面这段代码,我们为“泛型类型” Salary 定义一个方法 Min,细心的读者朋友们应该已经发现,方法的接收者除了类型名称之外,还有类型参数的形参 *Salary[T]

除此之外,语法上没有太大区别,需要注意的是“泛型方法”不能像“泛型函数”那样,具有自身的类型参数,以下代码目前是不支持的。

func (s *Salary[T]) Min[T1 int](x, y T) T {
    if x < y {
        return x
    }
    return y
}

04

 总结

本文我们介绍 Go v1.18 新增的“泛型”功能,介绍类型参数的语法和在函数、自定义类型和类型方法中的使用方式。

阅读完本文,读者朋友们应该对“泛型”有初步认识,并且可以简单运用。建议读者朋友们检查一下自己的项目代码,寻找可以使用“泛型”优化的代码片段

posted @ 2022-11-21 12:49  技术颜良  阅读(78)  评论(0编辑  收藏  举报