经过这几年的千呼万唤,简洁的Go语言终于在1.18版本迎来泛型编程。
在我看来泛型其实用C++的模板一词来描述就非常的准确。在写代码的时候,我们经常需要写很多重复的逻辑,一般这个时候我们就会使用函数来对其进行封装。但是由于Go是一种强类型语言,所以在定义和书写函数的时候需要在调用前标明类型。当然如果这一重复的逻辑只需要固定的类型,这样就足够了,但是很多时候我们需要不同的类型进行类似的逻辑,譬如我们刚刚看到的GIF。对于普通开发人员来说这种情况可能遇到的比较少,但是在一些库开发人员来说,这种情况变得非常的普遍。
泛型程序设计(generic programming)是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。Ada、Delphi、Eiffel、Java、C#、F#、Swift 和 Visual Basic .NET 称之为泛型(generics);ML、Scala 和 Haskell 称之为参数多态(parametric polymorphism);C++ 和 D称之为模板。具有广泛影响的1994年版的《Design Patterns》一书称之为参数化类型(parameterized type)。
例如:
vector v;
v.push_back(1);
中的int就指定了v中存储元素的类型为int, 也可以替换成其他你想要存储的类型
自定义模板, 只需要:
template
class Vector{
int mLen;
T* data;
};
又比如Java中也有泛型的概念:
class Vector{
public void push_back(T val){
//implement...
}
}
MyType[T1 constraint1 | constraint2, T2 constraint3...] ...
泛型的语法非常简单, 就类似于上面这样, 其中:
约束的意思是限定范围, constraint的作用就是限定范围, 将T限定在某种范围内
而常用的范围, 我们自然会想到的有:
这些约束, 不是被官方定义为内置类型, 就是被涵盖在了constraints包内。
下面是builtin.go的部分官方源码:
// 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, interfaces,
// 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 comparable
下面是constraints.go的部分官方源码:
// Integer is a constraint that permits any integer type.
// If future releases of Go add new predeclared integer types,
// this constraint will be modified to include them.
type Integer interface {
Signed | Unsigned
}
// Float is a constraint that permits any floating-point type.
// If future releases of Go add new predeclared floating-point types,
// this constraint will be modified to include them.
type Float interface {
~float32 | ~float64
}
//......
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
Signed约束就是这样被写出来的, 其中需要我们get的点有如下几个:
首先从最常用的max函数来看。 假设我现在需要两个int类型的最大值,原先的Go写法是:
package main
import "fmt"
func main() {
fmt.Println(maxInt(32, 64))
}
func maxInt(a, b int) int {
if a > b {
return a
}
return b
}
这个时候我需要增加一个获取float64的最大值,那么我们就要新增一个函数叫maxFloat64:
func maxFloat64(a, b float64) float64 {
if a > b {
return a
}
return b
}
每当我们需要对一种类型进行比较的时候,我们都需要重新编写一个函数,尽管他们的逻辑其实都是一样的,将来甚至还需要int8、int16、int32等等的类型。
当引入泛型之后,我们可以写成:
package main
import (
"fmt"
)
func main() {
fmt.Println(max(1, 2))
fmt.Println(max[int32](1, 2))
fmt.Println(max(1.5, 2.3))
}
func max[T int | int8 | int16 | int32 | int64 | float32 | float64](a, b T) T {
if a > b {
return a
}
return b
}
当然,后面跟了一长串的int|int8、、、这样实在有点费劲,如果我们之后需要增加一个min函数呢,岂不是又要把上面的这些抄一遍吗,所以这里还可以用interface提前定义下:
package main
import (
"fmt"
)
func main() {
fmt.Println(max(1, 2))
fmt.Println(max[int32](1, 2))
fmt.Println(max(1.5, 2.3))
fmt.Println(min(1, 2))
fmt.Println(min[int32](1, 2))
fmt.Println(min(1.5, 2.3))
}
type Number interface {
int | int8 | int16 | int32 | int64 | float32 | float64
}
func max[T Number](a, b T) T {
if a > b {
return a
}
return b
}
func min[T Number](a, b T) T {
if a < b {
return a
}
return b
}
除了函数声明中可以使用泛型,结构体中一样可以使用:
package main
import (
"fmt"
)
type Data[T comparable] struct {
Message T
}
func (d Data[T]) Print() {
fmt.Println(d.Message)
}
func main() {
d := Data[int]{
Message: 66,
}
d.Print()
}
这里有个类型:comparable
可以看看go的源码里面写的:
// 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 }
大概意思就是允许booleans, numbers, strings, pointers, channels, comparable类型组成的arrays以及所有字段都是由comparable类型组成的struct。
这里也有一个any类型:
// any is an alias for interface{} and is equivalent to interface{} in all ways.
type any = interface{}