Go语言学习笔记——Golang 1.18新特性泛型

时间:2024-10-10 14:37:58

文章目录

  • Golang 1.18新特性泛型
    • 一 什么是泛型
    • 二 Golang中的泛型
    • 三 泛型语法详解
      • 3.1 泛型的语法
      • 3.2 Constraint(约束)是什么
      • 3.3 自定义constraint(约束)
    • 四 泛型综合使用案例
      • 4.1 泛型切片
      • 4.2 泛型map
      • 4.3 泛型结构体
      • 4.4 泛型通道
      • 4.5 泛型函数
      • 4.6 泛型变量嵌套
      • 4.7 泛型方法
        • 4.7.1 接收器泛型
        • 4.7.2 方法参数泛型
      • 4.8 泛型接口
        • 4.8.1 基本接口泛型
        • 4.8.2 一般接口泛型


Golang 1.18新特性泛型

一 什么是泛型

泛型的英文是Generics,就是函数的参数,或者容器元素的类型,支持更广泛的类型,不再是特定的类型。

在Golang、Java、C++等这类静态语言中,是需要严格定义传入变量的类型的,斌不是随心所欲,例如在golang中:

func Sum(a, b int) int {
   
  return a + b
}

在函数Sum中,不仅要严格定义传入参数a和b的变量类型,而且返回值的类型也需要严格定义,所有你只能传入int类型进行调用:

Sum(1, 2) // 3

如果传入其它类型的变量就会报错:

fmt.Println(Sum(1.23, 2.54)); 
 
./main.go:33:18: cannot use 1.23 (untyped float constant) as int value in argument to Sum (truncated)
./main.go:33:24: cannot use 2.54 (untyped float constant) as int value in argument to Sum (truncated)

因此,如果当golang开发者想开发类似实现两个float类型变量相加的功能,只能另写一个函数:

func SumFloat(a, b float) float {
   
  return a + b
}

或者写一个通用的Sum函数使用interface反射来判断:

func Sum(a, b interface{
   }) interface{
   } {
   
  switch a.(type) {
   
  case int:
    a1 := a.(int)
    b1 := b.(int)
    return a1 + b1
  case float64:
    a1 := a.(float64)
    b1 := b.(float64)
    return a1 + b1
  default:
    return nil
  }
}

这样的话,不仅重复很多代码,而且类型频繁转换导致不仅性能低效,安全性上也不高。

所以泛型诞生了。

然而泛型是一把双刃剑,在给开发者带来便利的同时,同样会带来编译和效率的问题,因为泛型需要系统去推倒和计算变量的类型的,这在无形中会增加编译的时间和降低运行效率

二 Golang中的泛型

首先来看一下,在Golang 1.18版本中是如何利用泛型来实现Sum函数的

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

然后再调用一下:

fmt.Println(Sum[int](1, 2))  //3
fmt.Println(Sum[float64](1.23, 2.54))  //3.77

先不去理解函数中各组件的含义,仅仅看代码就简洁了不少,乙肝函数就实现了多个类型的功能。

因为泛型针对的是类型变量,在golang中,类型是贯穿整个语法生态的,比如:变量、函数、接口、通道等。

三 泛型语法详解

3.1 泛型的语法

MyType[T1 constraint1 | constraint2, T2 constraint3...] ... 

泛型的语法非常简单, 就类似于上面这样, 其中:

  • MyType可以是函数名, 结构体名, 类型名…
  • T1, T2…是泛型名, 可以随便取
  • constraint的意思是约束, 也是泛型中最重要的概念, 接下来会详解constraint
  • 使用 | 可以分隔多个constraint, T满足其中之一即可(如T1可以是constraint1constraint2中的任何一个)

3.2 Constraint(约束)是什么

约束的意思是限定范围, constraint的作用就是限定范围, 将T限定在某种范围内

而常用的范围, 我们自然会想到的有:

  • any(interface{}, 任何类型都能接收, 多方便啊!)
  • Interger(所有int, 多方便啊, int64 int32…一网打尽)
  • Float(同上)
  • comparable(所有可以比较的类型, 我们可以给所有可以比较的类型定制一些方法)

这些约束, 不是被官方定义为内置类型, 就是被涵盖在了constraints包内!!!

下面是的部分官方源码:

// 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

下面是的部分官方源码:

// 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
}
//......

3.3 自定义constraint(约束)

下面是constraints包中的官方源码:

type Signed interface {
   
	~int | ~int8 | ~int16 | ~int32 | ~int64
}

Signed约束就是这样被写出来的, 其中需要我们掌握的点有如下几个:

  • 使用interface{}就可以自定义约束
  • 使用 | 就可以在该约束中包含不同的类型, 例如int, int8, int64均满足Signed约束
  • 你可能会有疑问, ~是什么??? int我认识, ~int我可不认识呀??? 没关系, 实际上~非常简单, 它的意思就是模糊匹配, 例如:
    • type MyInt int64
    • 此时 MyInt并不等同于int64类型(Go语言特性)
    • 若我们使用int64来约束MyInt, 则Myint不满足该约束
    • 若我们使用~int64来约束MyInt, 则Myint满足该约束(也就是说, ~int64只要求该类型的底层int64, 也就是模糊匹配了)
    • 官方为了鲁棒性, 自然把所有的类型前面都加上了~

例如:

type My_constraint_Num interface {
   
	~