关于go:17-GolangGo语言快速入门泛型

  Golang在1.18版本反对了泛型,写过java/c++等语言的可能对泛型有肯定的理解。那么泛型到底是什么呢?他有什么作用呢?

为什么须要泛型

  为什么须要泛型呢?Golang是强类型语言,任何变量或者函数参数,都须要定义明确的参数类型。假如咱们须要实现这么一个函数,输出两个参数,函数返回其相加的值,输出参数能够是两个整型int,浮点数float,还有可能是字符串等等,这时候通常怎么办?定义多个函数实现吗?如上面程序所示:

//定义多个函数实现
func twoIntValueSum(a, b int) int {
    return a + b
}

func twoFloatValueSum(a, b float32) float32 {
    return a + b
}

func twoStrValueSum(a, b string) string {
    return a + b
}

//定义一个函数,类型是interface{}

  这样就可能导致存在大量反复代码,而且调用方还须要依据参数类型决定调用哪一个办法。还能怎么办呢?只定义一个函数,只是参数是interface{},函数外部通过反射等形式,执行对应的操作,如上面程序:

func twoValueSum(a, b interface{}) (interface{}, error)  {
    if reflect.TypeOf(a).Kind() != reflect.TypeOf(b).Kind() {
        return nil, errors.New("two value type different")
    }

    switch reflect.TypeOf(a).Kind() {
    case reflect.Int:
        return reflect.ValueOf(a).Int() + reflect.ValueOf(b).Int(), nil
    case reflect.Float64:
        return reflect.ValueOf(a).Float() + reflect.ValueOf(b).Float(), nil
    case reflect.String:
        return reflect.ValueOf(a).String() + " " + reflect.ValueOf(b).String(), nil
    default:
        return nil, errors.New("unknow value type")
    }
}

  应用反射实现的话,依赖反射性能较低,二来能够看到输出参数和返回值都是interface{},应用方还须要多执行一步返回值类型转换,而且反射相对而言还是比较复杂的。

泛型初体验

  那么还有其余什么方法吗?这就要说到Go 1.18版本实现的泛型了,泛型相当于定义了一个函数模板,真正调用函数的时候,再确定参数以及返回值等具体类型,基于泛型实现上述性能如下:

package main

import "fmt"

func main() {

    ret := twoValueSum[int](100, 200)
    fmt.Println(ret)

    ret1 := twoValueSum[string]("hello ", "world")
    fmt.Println(ret1)
}

func twoValueSum[T int | float64 | string](a T, b T) T {
    return a + b
}

  泛型类型或者泛型函数定义的语法格局能够形容为[Identifier TypeConstraint],上述程序中的T就是标识符(Identifier),int等就是TypeConstraint(类型限度,也就是说twoValueSum函数的输出参数类型只能是这几种,不能是其余的),留神在调用具体函数时,须要申明真正的类型。

  上面举几个泛型程序事例,介绍下泛型类型常见定义形式(泛型函数参考twoValueSum函数定义):

//定义切片类型,元素类型能够是int,float64或者string
type Slice[T int|float64|string ] []T
//实例化变量arr1,留神申明了切片元素类型为int
var arr1 Slice[int] = []int{1, 2, 3} 
//实例化变量arr2,留神申明了切片元素类型为string
var arr2 Slice[string] = []string{"Hello", "World"} 

//自定义map类型,key只能是string,value能够是int、float32或者float64
type DefineMap[KEY string, VALUE int | float32 | float64] map[KEY]VALUE  
var m DefineMap[string, int] = map[string]int{
    "zhangsan": 89,
    "lisi":  80,
}

  再举一个例子,假如想实现一个切片比拟的函数(相似于字符串的字典序比拟),该怎么定义呢?参数能够是[]int,[]float,[]string等等,只有元素可比拟即可。参考官网给的事例:

//定义浮点数类型汇合,
type Float interface {
    ~float32 | ~float64
}
//定义有符号整数汇合
type Signed interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}
//定义无符号整数汇合
type Unsigned interface {
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
//定义整数汇合
type Integer interface {
    Signed | Unsigned
}

//定义可比拟类型汇合:整数,浮点数,字符串
type Ordered interface {
    Integer | Float | ~string
}

//Compare函数须要输出两个切片,切片元素类型必须是可排序的,这里限度为类型Ordered
func Compare[E Ordered](s1, s2 []E) int
//二分法实现函数,输出切片以及查找元素,切片元素类型必须是可排序的,这里限度为类型Ordered
func BinarySearch[E Ordered](x []E, target E) (int, bool)

  留神目前Go语言规范库还没有应用泛型(Go作者不倡议),不过有几个试验库应用了泛型,有趣味的读者能够查阅:

golang.org/x/exp/constraints
Constraints that are useful for generic code, such as constraints.Ordered.

golang.org/x/exp/slices
A collection of generic functions that operate on slices of any element type.

golang.org/x/exp/maps
A collection of generic functions that operate on maps of any key or element type.

  另外,留神符号 ~ ,这是什么意思呢?假如咱们定义一个泛型切片类型限度蕴含int,另外还有一个自定义类型(其实也是int),自定义类型能用来结构该切片吗?如下:

type Slice[T int | float64 | string] []T
type Integer int

var arr Slice[Integer] = []Integer{1,2,3}

//Integer does not implement int|float64|string (possibly missing ~ for int in constraint int|float64|string)

  留神尽管自定义类型Integer其实也就是int,然而这两种类型是不相等的,所以这里才有语法错误”Integer does not implement int”,针对这种状况,Go语言给出的倡议是应用符号 ~ 定义,如:

type Slice[T ~int| ~ float64 | ~string ] []T
type Integer int

//编译通过
var arr Slice[Integer] = []Integer{1,2,3}

  更多的泛型语法,以及应用场景,有趣味的读者持续钻研,这里就不一一介绍了。

泛型函数底层是怎么实现的

  最初再思考一个问题,Go语言是如何实现上述泛型事例呢?为什么只定义一个函数实现,就能传递多种类型参数呢?为什么只定义一种变量类型,却能实例化多种类型的变量呢?

  咱们以上面的程序为例,看一下编译后的汇编代码,就明确其实现原理了:

package main

import "fmt"

func main() {

    ret := twoValueSum[int](100, 200)
    fmt.Println(ret)

    ret1 := twoValueSum[string]("hello ", "world")
    fmt.Println(ret1)
}


func twoValueSum[T int | float64 | string](a T, b T) T {
    return a + b
}

//go tool compile -S -N -l test.go

"".main STEXT
    //main函数中的函数调用替换了!
    0x0037 00055 (test.go:7)    CALL    "".twoValueSum[go.shape.int_0](SB)
    0x00e4 00228 (test.go:10)    CALL    "".twoValueSum[go.shape.string_0]

//编译阶段生成的两个具体的函数
"".twoValueSum[go.shape.int_0] STEXT
"".twoValueSum[go.shape.string_0] STEXT

  能够看到,咱们只定义了一个函数实现twoValueSum,然而Go编译器为咱们生成了两个具体的函数(因为咱们调用了这两种函数实现),而针对twoValueSum的函数调用,也都在编译过程被替换了。

总结

  须要留神的是,Golang尽管在1.18版本反对了泛型,然而还是不倡议在规范库应用,毕竟这次代码变动较大,而且后续泛型可能还会有较大变动,所以线上应用泛型需谨慎。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理