文章

泛型

泛型

package main

import "fmt"

func main() {
   strs := []string{"yama", "jiang"}
   is := []int{1, 2}
   fs := []float64{3.12, 123.4}
   printArray(strs)
   printArray(is)
   printArray(fs)
}

//我们不限定类型,让调用者自己取定义类型 T
//[]T 形式类型 实际类型
/*、
内置的泛型类型any和comparable
any:表示go里面所有的内置基本类型,等价于 interface{}
comparable:表示go里面所有内置的可比较类型:int,uint,float,bool,struct,指针等一切可以比较的类型
*/
func printArray[T any](arr []T) {
   for _, v := range arr {
      fmt.Print(" ", v)
   }
}

image-20230105003830679

泛型的作用

泛型减少重复代码并提高类型安全性

在下面情景的时候非常适合使用泛型:当你需要针对不同类型书写同样的逻辑,使用泛型来简化代码是最好的。

泛型类型

只定义一个类型就能代表所有的类型,需要使用到泛型:

type Slice[T int|float32|float64] []T

不同于一般的类型定义,这里类型名称Slice后带了中括号,对各个部分做一个解说就是:

  • T就是上面介绍过的类型型参(Type parameter),在定义Slice类型的时候T代表的具体类型并不确定,类似一个占位符
  • int|float32|float64 这部分被称为类型约束(Type constraint),中间的 | 的意思是告诉编译器,类型形参T只可以接收int或float32或float64这三种类型的实参
  • 中括号里的T int|float32|float64 这一整串因为定义了所有的类型形参,所以我们称其为 类型形参列表(type parameter list)
  • 这里新定义的类型名称叫Slice[T]

这种类型定义的方式中带了类型形参,很明显和普通的类型定义非常不一样,所以我们将这种类型定义中带类型形参的类型,称之为==泛型类型==

package main

import "fmt"

func main() {
   //Slice[T]
   type Slice[T int | float32 | float64] []T

   var a Slice[int] = []int{1, 2, 3}
   fmt.Println(a)
   fmt.Printf("Type Name:%T", a)

   var b Slice[float32] = []float32{1, 2, 3}
   fmt.Println(b)
   fmt.Printf("Type Name:%T", b)

   var c Slice[float64] = []float64{1, 2, 3}
   fmt.Println(c)
   fmt.Printf("Type Name:%T", c)

   type MyMap[KEY int | string, VALUE float32 | float64] map[KEY]VALUE
   var m1 MyMap[string, float64] = map[string]float64{
      "go":   90,
      "java": 80,
   }
   fmt.Println(m1)
}

image-20230105130205578

所有类型定义都可使用类型形参,所以下面这种结构体以及接口的定义也可以使用类型形参:

//一个泛型类型的结构体,可用int 或 string 类型实例化
type MyStruct[T int|string] struct{
  id T
  Name string
}
//一个泛型接口
type IPrintData[T int|float32|string] interface{
  Print(data T)
}
//一个泛型通道,可用类型实参 int 或 string 实例化
type MyChan[T int|string] chan T

特殊的泛型

type Wow[T int | string] int
var aa Wow[int] = 123 // 编译正确
var bb Wow[string] = 123 // 编译正确
//var cc Wow[string] = "hello"//编译错误,因为“hello”不能赋值给底层类型int
fmt.Println(aa, bb)
//fmt.Println(cc)

这里虽然使用了类型形参,但因为类型定义是type Wow[T int | string] int,所以无论传入什么类型实参,实例化后的新类型的底层类型都是int,所以int类型的数字123可以赋值给变量aa,bb,但string类型的字符串“hello”不能复制给cc

泛型函数

package main

import "fmt"

type MySlice[T int | float64] []T

// 给泛型添加方法
func (s MySlice[T]) Sum() T {
   var sum T
   for _, v := range s {
      sum += v
   }
   return sum
}

// 泛型函数
//func Add(a int, b int) int {
// return a + b
//}

func Add[T int | float64 | string](a T, b T) T {
   return a + b
}
func main() {
   var s MySlice[int] = []int{1, 2, 3}
   fmt.Println(s.Sum())
   var f MySlice[float64] = []float64{1.4, 2.2, 3.9}
   fmt.Println(f.Sum())
   fmt.Println(Add[int](4, 8))
   fmt.Println(Add[float64](4.4, 8.3))
   fmt.Println(Add[string]("123", "yama"))
   fmt.Println(Add(4, 2))
}

image-20230106125553107

Go的泛型(或者类型形参)目前可使用在3个地方:

  • 泛型类型 - 类型定义中带类型形参的类型
  • 泛型receiver - 泛型类型的receiver
  • 泛型函数 - 带类型形参的函数

自定义泛型类型

如果类型太多了的话,就使用自定义泛型类型

package main

import "fmt"

type int8AAA int8

//别名的情况 要么在MyInt后面加入int8AAA,要么在int8前面加一个~

// 自定义泛型
type MyInt interface {
   //int | int8 | int16 | int32 | int64 | int8AAA
   int | ~int8 | int16 | int32 | int64
}

func GetMaxNum[T MyInt](a, b T) T {
   if a > b {
      return a
   }
   return b
}
func main() {
   var a int = 10
   var b int = 20
   fmt.Println(GetMaxNum(a, b))
   fmt.Println(GetMaxNum[int](a, b))
   var c int8AAA = 30
   var d int8AAA = 40
   fmt.Println(GetMaxNum(c, d))

}

image-20230106130904685

理解泛型三大要素:类型参数、类型集合和类型推断

License:  CC BY 4.0 test