形参和实参

假设我们有个计算两数之和的函数

  1. func Add(a int, b int) int {
  2. return a + b
  3. }

这个函数很简单,但是它有个问题——无法计算int类型之外的和

如果我们想计算浮点或者字符串的和该怎么办?解决办法之一就是像下面这样为不同类型定义不同的函数

  1. func AddFloat32(a float32, b float32) float32 {
  2. return a + b
  3. }
  4. func AddString(a string, b string) string {
  5. return a + b
  6. }

类型定义

  1. type IntSlice []int
  2. type Int32Slice []int32
  3. type Float32Slice []float32

定义了一个类型IntSlice,此类型为可以容纳int类型的切片。
如果此时想定一个可以容纳int32类型的切片或者float32类型的切片,需要给每种类型定义新类型.

使用泛型后,只定义一个类型就能代表上面的所有类型

  1. type MySlice[T int | int32 | float32] []T
  • MySlice名称后有中括号”[]
  • T为类型形参,T的具体类型并不确定,类似占位符
  • int | int32 | float32被称为类型约束,类型T只能接收int、int32、float32这三种类型的类型实参
  • 中括号”[]”里的被称为类型形参列表,MySlice中只有一个T
  • 定义的泛型类型名称为MySlice[T]
    1. var a MySlice[int] = []int{1, 2, 3}
    2. var b MySlice[float32] = []int{1.0, 2.0}

函数定义

上面两数之和

  1. package main
  2. import "fmt"
  3. func add[T int | int32 | float64 | string](a, b T) T {
  4. return a + b
  5. }
  6. func main() {
  7. fmt.Println(add(1, 3))
  8. // 字符串拼接
  9. fmt.Println(add("A", "B"))
  10. var int32A int32 = 3
  11. var int32B int32 = 4
  12. res := add(int32A, int32B)
  13. fmt.Println(res)
  14. }
  1. 4
  2. AB
  3. 7

指针参数

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. func add[T int | int32 | float64 | string](a, b T) T {
  7. return a + b
  8. }
  9. func init() {
  10. fmt.Println(add(1, 3))
  11. fmt.Println(add("A", "B"))
  12. var int32A int32 = 3
  13. var int32B int32 = 4
  14. res := add(int32A, int32B)
  15. fmt.Println(res)
  16. fmt.Println("\n")
  17. }
  18. // 获取指针
  19. func ptr[T any](in T) *T {
  20. return &in
  21. }
  22. // 获取指针值
  23. func ptrValue[T any](in *T) T {
  24. return *in
  25. }
  26. func main() {
  27. intPtr1 := ptr[int](100)
  28. fmt.Println(*intPtr1, reflect.TypeOf(intPtr1))
  29. intPtr2 := ptr(200)
  30. fmt.Println(*intPtr2, reflect.TypeOf(intPtr2))
  31. strPtr := ptr("abc")
  32. fmt.Println(reflect.TypeOf(strPtr))
  33. strValue := ptrValue(strPtr)
  34. fmt.Println(strValue, reflect.TypeOf(strValue))
  35. }
  1. 4
  2. AB
  3. 7
  4. 100 *int
  5. 200 *int
  6. *string
  7. abc string

泛型实现简单说明

泛型的引入提高了Go的编码效率,但 编码效率编译速度运行速度 三者是无法同时提高的。Go引入泛型后,会提高编码效率,但编译速度和运行速度会有略微降低。可以通过了解Go的泛型实现,从而了解为什么会降低编译速度和运行速度。
实现泛型的两种常见方式为“虚拟方法表”“单态化”

虚拟方法表(Virtual Method Table)

泛型函数,在编译时都会被编译器改成只接受指针作为参数,因为指针都是一样的,所以可以只编译一份代码。这些指针的值都被分配到堆上,调用泛型函数时会将指针传递给泛型函数。
当传入的对象,且要调用对象的方法时,由于只有指向对象的指针,不知道方法在哪,所以需要一个查询方法的内存地址的表格“Virtual Method Table”,根据table查询到对应的方法地址。指针值的推导和调用虚拟函数,会比直接调用函数慢。

单态化(Monomorphization)

编译器为每个被调用传入的数据类型,生成一个泛型函数的副本。对应的编译速度会慢,但运行时性能会更快,跟不使用泛型一致。

Go的实现

go采用了两者结合的实现。

  • 对于值类型,比如int、int32、struct{} 等,会分别生成一份对应的代码副本。且int和type MyInt int,其底层类型都是int,只会生成同一个副本
  • 对于指针类型和接口类型的调用,会统一生成一份泛型方法的副本。调用到方法时根据方法表动态查找方法地址

综上可以理解为什么Go泛型引入后,编译速度和运行速度都会略微下降了。

Go泛型使用

基础

上述Go的泛型-类型定义、函数定义是泛型的基础。
且Go 1.18因为泛型,引入了一些新的变化。

interface{}、any、comparable

这这里的interface不是方法集的interface,而是指空接口interface{},从Go 1.18后,空接口interface{}的定义发生了变更,它可以表示所有类型的集合

  • 所有类型的集合,并非空集
  • 类型约束中的空接口interface{},表示包含了所有类型的类型集,并不是限定只能传入空接口interface{}
  1. type Slice[T interface{}] []T
  2. var s1 Slice[int]
  3. var s2 Slice[string]
  4. var s3 Slice[interface{}]

由于空接口interface{}的定义发生了变更,不能直接体现实际语义,所以Go 1.18提供了新的等价关键词any。
且Go官方推荐所有的使用空接口的地方都使用any替换。

  1. type any = interface{}
  2. // 上述的Slice可以改写成
  3. type Slice[T any] []T

Go 1.18内置comparable约束,表示所有可以用 != 和 == 对比的类型。

  1. // 可以用comparable来做泛型Map的key
  2. type Map[K comparable, V any] map[K]V

符号“~”

符号“~”,指底层类型约束,举个例子。

  1. type Slice[T int | int32] []T
  2. type MyInt int
  3. var a Slice[MyInt] // 错误

MyInt底层类型是int,但其本身并不是int,所以不能用于Slice[T int | int32]的实例化。
可以使用 ~int 的写法,表示所有以int为底层类型的类型都可以用于实例化。

  1. type Slice[T ~int | ~int32] []T
  2. type MyInt int
  3. var a Slice[MyInt] // 正确

泛型类型

类型形参不能单独使用

下面这种是无法定义泛型Generic[T]的。

  1. type Generic[T int | int32] T

泛型数据结构

类型形参不能单独使用,需要跟其他数据结构组合起来一起定义泛型。
可以组合的有ptr、slice、array、struct、map、channel、interface。

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. type myInt interface {
  7. ~int | ~int32 | ~int64
  8. }
  9. // ptr、slice、struct、map、channel、interface
  10. type Ptr[T int | int32] *T // ptr
  11. type SliceInt[T myInt] []T // slice
  12. type MapInt[K myInt, V any] map[K]V // map
  13. type ChannelInt[T myInt] chan T // chan
  14. // 只要满足有方法 Val() T,T的约束为 ~int | ~int32 | ~int64的都为这个接口的实现
  15. type InterfaceInt[T myInt] interface { // interface
  16. Val() T
  17. }
  18. // 例子1,InterfaceInt的实现
  19. type StructInt[T myInt] struct { // struct
  20. Data T
  21. }
  22. func (s *StructInt[T]) Val() T {
  23. return s.Data
  24. }
  25. // 例子2,InterfaceInt的实现
  26. type InterfaceIntImpl1 struct{}
  27. func (i *InterfaceIntImpl1) Val() int {
  28. return 1
  29. }
  30. // 例子3,InterfaceInt的实现
  31. type InterfaceIntImpl2 int32
  32. func (i InterfaceIntImpl2) Val() int32 {
  33. return int32(i)
  34. }
  35. // 测试
  36. func main() {
  37. var interfaceInt InterfaceInt[int]
  38. interfaceInt = &InterfaceIntImpl1{}
  39. val := interfaceInt.Val()
  40. fmt.Println(val)
  41. fmt.Println(reflect.TypeOf(val)) // int
  42. interfaceInt = &StructInt[int]{Data: 2}
  43. val2 := interfaceInt.Val()
  44. fmt.Println(val2)
  45. fmt.Println(reflect.TypeOf(val2)) // int
  46. var interfaceInt32 InterfaceInt[int32] = InterfaceIntImpl2(int32(2))
  47. valInt32 := interfaceInt32.Val()
  48. fmt.Println(valInt32)
  49. fmt.Println(reflect.TypeOf(valInt32)) // int32
  50. }
  1. 1
  2. int
  3. 2
  4. int
  5. 2
  6. int32

指针类型约束

指针类型泛型定义约束,不能直接定义,需消除歧义。

  1. // type Test1 [T * int] []T // error 会当做 T 乘 int
  2. // 可以用逗号消除歧义
  3. type Test2[T *int,] []T
  4. type Test3[T *int | *int32,] []T
  5. // 推荐写法
  6. type Test4[T interface{ *int | *int32 }] []T

获取具体类型方式

在使用泛型时,获取泛型具体的类型,可以通过使用反射或者any转换获取。

  1. func GetType[T int | string](t T) {
  2. // t.(int) // error,泛型类型定义的变量不能使用类型断言
  3. // 1. 反射
  4. v := reflect.ValueOf(t)
  5. switch v.Kind() {
  6. case reflect.Int:
  7. default:
  8. }
  9. // 2. 转换
  10. var i any = t
  11. switch i.(type) {
  12. case int:
  13. default:
  14. }
  15. }

实际开发中如果用到这种,需要慎重考虑是否需要使用泛型。
泛型的出现是为了屏蔽具体类型或者避免使用反射,现在又在泛型中使用反射或者any转换获得具体类型,这种做法是不合适的。

泛型函数

带类型形参的函数,被称为泛型函数。

简单泛型函数

比较常用的泛型函数形式如下。

  1. func Add[T int | int32](a, b T) T {
  2. return a + b
  3. }
  4. // 实例化使用
  5. Add[int](1,1) // 声明实例化类型为int
  6. Add(1,1) // 类型推断

匿名函数

匿名函数无法自己定义类型形参,但可以使用定义好的类型形参。

  1. // 1. 匿名函数不能自己定义类型形参
  2. //func test() {
  3. // fn1 := func[T int | int32](a, b T) T {
  4. // return a + b
  5. // }
  6. //}
  7. // 2. 匿名函数可以使用定义好的类型形参
  8. func test[T int | int32](a, b T) T {
  9. result := func(a, b T) T {
  10. return a + b
  11. }(a, b)
  12. return result
  13. }

函数闭包

可以使用函数闭包实现一些高级功能,比如下面泛型Filter、Map、Reduce的实现。

  1. func Filter[T any](src []T, f func(T) bool) []T {
  2. res := make([]T, 0)
  3. for _, t := range src {
  4. if f(t) {
  5. res = append(res, t)
  6. }
  7. }
  8. return res
  9. }
  10. func Map[S, T any](src []S, f func(S) T) []T {
  11. res := make([]T, 0)
  12. for _, s := range src {
  13. t := f(s)
  14. res = append(res, t)
  15. }
  16. return res
  17. }
  18. func Reduce[T any](src []T, f func(T, T) T) T {
  19. if len(src) == 1 {
  20. return src[0]
  21. }
  22. return f(src[0], Reduce(src[1:], f))
  23. }
  24. // 测试函数闭包
  25. func main() {
  26. // filter test
  27. filterTest := Filter[int]([]int{1, 2, 3, 4, 5}, func(i int) bool {
  28. if i > 3 {
  29. return true
  30. }
  31. return false
  32. })
  33. fmt.Println(filterTest)
  34. // map test
  35. mapTest := Map[int, string]([]int{1, 2, 3}, func(i int) string {
  36. return "str" + strconv.Itoa(i)
  37. })
  38. fmt.Println(mapTest)
  39. // reduce test
  40. reduceTest := Reduce([]int{1, 2, 3}, func(a int, b int) int {
  41. return a + b
  42. })
  43. fmt.Println(reduceTest)
  44. }

泛型结构体

结构体

泛型结构体是上面泛型数据结构的一种,这里单讲一下。
定义一个支持Map的结构体,key为comparable,value为any。

  1. type Map[K comparable, V any] struct {
  2. Data map[K]V
  3. }
  4. func NewMap[K comparable, V any]() *Map[K, V] {
  5. return &Map[K, V]{
  6. Data: make(map[K]V),
  7. }
  8. }
  9. func (m *Map[K, V]) Set(key K, value V) {
  10. m.Data[key] = value
  11. }
  12. func (m *Map[K, V]) Get(key K) V {
  13. return m.Data[key]
  14. }
  15. func (m *Map[K, V]) Exist(key K) bool {
  16. _, ok := m.Data[key]
  17. return ok
  18. }
  19. func (m *Map[K, V]) PrintAll() {
  20. for k, v := range m.Data {
  21. fmt.Println("key: ", k, ", val: ", v)
  22. }
  23. }
  24. // 使用
  25. intStringMap := NewMap[int, string]()
  26. intStringMap.Set(1, "a")
  27. intStringMap.Set(2, "b")
  28. intStringMap.PrintAll()
  29. s1 := &Student{
  30. Num: 1,
  31. Name: "a",
  32. }
  33. s2 := &Student{
  34. Num: 2,
  35. Name: "b",
  36. }
  37. numStudentMap := NewMap[int, *Student]()
  38. numStudentMap.Set(s1.Num, s1)
  39. numStudentMap.Set(s2.Num, s2)
  40. numStudentMap.PrintAll()

定义的struct map,可以兼容所有使用到map的场景,且提供统一的方法。比如PrintAll()方法,无需跟之前一样每个不同的map都要自己写一遍。

泛型方法

泛型方法无法定义类型形参,只能通过receiver使用类型形参。

  1. // 不支持泛型方法
  2. // func (m *Map[K, V]) TestGeneric[T int | string](a, b T) T { // error
  3. // return a + b
  4. // }
  5. //
  6. // 只能通过receiver使用类型形参
  7. func (m *Map[K, V]) Equal(a, b K) bool {
  8. return a == b
  9. }

匿名结构体

匿名结构体是不支持泛型的。

  1. // 匿名结构体不支持泛型
  2. //testCase := struct [T int | string] { // error
  3. // a T
  4. //}[int] {
  5. // a: 1
  6. //}

接口

接口比较复杂,在go 1.18以后,go的接口分为了两种,分别是基本接口一般接口

基本接口

只包含方法,是方法的集合。基本接口可以定义变量

非泛型基本接口

不包含泛型的基本接口。举例

  1. type BasicInterface interface {
  2. Name() string
  3. Age() int
  4. }

可以定义变量。

  1. // 可以定义变量
  2. var a BasicInterface

基本接口本身也代表一个类型集,可以用在类型约束中。

  1. // 基本接口也代表一个类型集,可以用在类型约束中
  2. type ATest[T BasicInterface] []T
  3. // 可以当做类型集,用在泛型方法
  4. func BasicInterfaceFunc1[T BasicInterface](b T) {
  5. b.Name()
  6. b.Age()
  7. }
  8. // 可以跟go1.18之前写法一致
  9. func BasicInterfaceFunc2(b BasicInterface) {
  10. b.Name()
  11. b.Age()
  12. }

泛型基本接口

包含泛型的基本接口。举例

  1. type BasicInterface2[T int | int32 | string] interface {
  2. Func1(in T) (out T)
  3. Func2() T
  4. }

可以定义变量

  1. var b1 BasicInterface2[int]
  2. var b2 BasicInterface2[string]

也代表一个类型集,可以用在类型约束中。

  1. // 可以用在类型约束
  2. type BTest1[T BasicInterface2[int]] []T
  3. type BTest2[T int] BasicInterface2[T]
  4. // 可以当做类型集,用在泛型方法
  5. func BTestFunc1[T BasicInterface2[int]](t T) {
  6. t.Func2()
  7. }
  8. // 可以跟go1.18之前写法一致
  9. func BTestFunc2(t BasicInterface2[int]) {
  10. t.Func2()
  11. }

如何实现泛型基本接口?对于BasicInterface2来说,需要满足以下条件。

  • 有方法 Func1(in T) (out T),方法 Func2() T。
  • T满足约束为 int | int32 | string。
  • 方法的T,同时只能为一种类型。比如Func1中的T为int,Func2中的T也只能为int。
  1. // 举例1-是BasicInterface2的实现
  2. type BasicInterface2Impl struct{}
  3. func (b BasicInterface2Impl) Func1(in int) (out int) {
  4. panic("implement me")
  5. }
  6. func (b BasicInterface2Impl) Func2() int {
  7. panic("implement me")
  8. }
  9. // 举例2-不是BasicInterface2的实现
  10. type BasicInterface2Impl2 struct{}
  11. func (b BasicInterface2Impl2) Func1(in string) (out string) {
  12. panic("implement me")
  13. }
  14. func (b BasicInterface2Impl2) Func2() int {
  15. panic("implement me")
  16. }
  17. // 举例3-不是BasicInterface2的实现
  18. type BasicInterface2Impl3 struct{}
  19. func (b BasicInterface2Impl3) Func1(in float32) (out float32) {
  20. panic("implement me")
  21. }
  22. func (b BasicInterface2Impl3) Func2() float32 {
  23. panic("implement me")
  24. }

一般接口

包含类型约束的接口都被称为一般接口,无论是否包含方法。一般接口无法定义变量

简单约束和集合操作

一般常用的是将类型约束定义在接口中,且多个类型约束接口可以进行集合操作。

  1. type CommonInterface interface {
  2. int | int8 | float32 | string
  3. }
  4. // 不能用来定义变量
  5. //var commonInterface CommonInterface // error
  6. type Int interface {
  7. int | int8 | int32 | int64
  8. }
  9. type Float interface {
  10. float32 | float64
  11. }
  12. // 并集操作
  13. type IntAndFloat interface {
  14. Int | Float
  15. }
  16. // 交集操作
  17. type IntExceptInt8 interface {
  18. Int
  19. int8
  20. }
  21. // 空集,无实际意义
  22. type Null interface {
  23. int
  24. int32
  25. }

复杂类型约束

上述为比较简单的类型约束,一般接口也可以包含方法,也可以包含泛型。

复杂约束1:包含方法的一般接口

  1. type CommonInterface2 interface {
  2. ~int | ~int8 | ~struct {
  3. Data string
  4. }
  5. Func1() string
  6. }

不能用来定义变量

  1. //var c CommonInterface2 // error

如何实现或者实例化复杂接口?对上述CommonInterface2来说,需要满足以下条件。

  • 底层类型为int|int8|struct{Data string}
  • 有方法Func1() string
  1. // 举例1 是CommonInterface2的实例化
  2. type CommonInterface2_1 int
  3. func (c CommonInterface2_1) Func1() string {
  4. return "CommonInterface2_1"
  5. }
  6. // 举例2 是CommonInterface2的实例化
  7. type CommonInterface2_2 struct {
  8. Data string
  9. }
  10. func (c CommonInterface2_2) Func1() string {
  11. return c.Data
  12. }
  13. // 举例3 不是CommonInterface2的实例化
  14. type CommonInterface2_3 int32
  15. func (c CommonInterface2_3) Func1() string {
  16. panic("CommonInterface2_3")
  17. }
  18. // 针对CommonInterface2的泛型方法
  19. func DoCommonInterface2[T CommonInterface2](t T) {
  20. fmt.Println(t.Func1())
  21. }

测试例子

  1. commonInterface2_1 := CommonInterface2_1(1)
  2. DoCommonInterface2[CommonInterface2_1](commonInterface2_1)
  3. DoCommonInterface2(commonInterface2_1) // 类型推断
  4. commonInterface2_2 := CommonInterface2_2{}
  5. DoCommonInterface2(commonInterface2_2)
  6. //commonInterface2_3 := CommonInterface2_3(1)
  7. //DoCommonInterface2(commonInterface2_3) // error, commonInterface2_3不是CommonInterface2的实例化

复杂约束2:包含方法,且包含泛型的一般接口

  1. type CommonInterface3[T string | float32] interface {
  2. ~int | ~int8 | ~struct {
  3. Data T
  4. }
  5. Func2() T
  6. }

不能用来定义变量。

  1. //var a CommonInterface3[string]

如何实现或者实例化复杂接口?对上述CommonInterface3来说,需要满足以下条件。

  • 底层类型为int|int8|struct{Data T} ,T的约束为string | float32
  • 有方法Func2() T, T的约束为string | float32
  • 如果底层类型定义的没有T, 则Func2() T的约束可以为string | float32
  • 如果底层类型定义的有T, 比如struct{Data T}, 此时Func2() T,两个T需要同时为string或者同时为float32
  1. // 举例1 CommonInterface3的实例化
  2. type CommonInterface3Impl1 int
  3. func (c CommonInterface3Impl1) Func2() string {
  4. return strconv.Itoa(int(c))
  5. }
  6. // 举例2 CommonInterface3的实例化
  7. type CommonInterface3Impl2 int
  8. func (c CommonInterface3Impl2) Func2() float32 {
  9. return float32(c)
  10. }
  11. // 举例3 CommonInterface3的实例化
  12. type CommonInterface3Impl3[T string | float32] struct {
  13. Data T
  14. }
  15. func (c CommonInterface3Impl3[T]) Func2() T {
  16. return c.Data
  17. }
  18. // 针对CommonInterface3[string]的泛型函数
  19. func DoCommonInterface3_1[T CommonInterface3[string]](t T) {
  20. fmt.Println(reflect.TypeOf(t.Func2()))
  21. }
  22. // 针对CommonInterface3[float32]的泛型函数
  23. func DoCommonInterface3_2[T CommonInterface3[float32]](t T) {
  24. fmt.Println(reflect.TypeOf(t.Func2()))
  25. }
  26. // 针对CommonInterface3[T]的泛型函数
  27. // 新增一个泛型D, 用来表示CommonInterface3里的泛型
  28. func DoCommonInterface3[D string | float32, T CommonInterface3[D]](t T) {
  29. fmt.Println(reflect.TypeOf(t.Func2()))
  30. }

测试例子

  1. commonInterface3_1 := CommonInterface3Impl1(1)
  2. DoCommonInterface3_1(commonInterface3_1)
  3. DoCommonInterface3[string](commonInterface3_1)
  4. commonInterface3_2 := CommonInterface3Impl2(1)
  5. DoCommonInterface3_2(commonInterface3_2)
  6. DoCommonInterface3[float32](commonInterface3_2)
  7. commonInterface3_3 := CommonInterface3Impl3[string]{
  8. Data: "data",
  9. }
  10. DoCommonInterface3_1(commonInterface3_3)
  11. DoCommonInterface3[string](commonInterface3_3)

原文:https://zhuanlan.zhihu.com/p/595825730