敞开生长之旅!这是我参加「日新计划 2 月更文挑战」的第 30 天,点击查看活动概况

1 泛型

  • 简介

通常在go中运用interface 空接口 能够用作泛型的支撑。

1.18.1 之后的版别的golang言语 现已支撑泛型。

那么,泛型能够提高什么?

对任何元素类型的切片,映射,通道进行操作的函数。

对切片或map 元素 进行计算的函数,例如最大,最小,平均,模式,标准偏差.

切片或map 的转换函数,如缩放切片.
在channel 通道运行的功能,例如将两个通道组合为一个通道.

  • 类型近似

类型近似,用~(波浪号)符号

  `~`  `~`

通用数据结构,如集合,多map,并发散列图,图,树,链表.
对函数进行操作的函数,例如并行调用给定函数并回来一部分成果.

当公共办法的完成对于每种类型看起来都相一起。

1.1 运用反射, 防止运用泛型类型参数

当每个类型都有不同的 办法时,运用反射将 interface 中的类型转换为 []byte

   json.Marshal(inter)

假如算法调用一组特定办法就满足,那么运用特定接口仍然时最好的办法
特别是当每个类型的通用办法完成不一起。

如 io.Reader 之类的通用接口将无处可用。
还应考虑运用反射对传递的数据进行拆箱,因为它可简化API的运用
例如: 将数据作为 空接口的 json.Marshal()函数对用户来说非常方便。

修正它以运用类型参数将危害这种情况,因为传递数据需求完成特定办法。

1.2 自定义泛型:变长参数支撑

在某些场景下,目前依然能运用反射来完成,比如泛型。
因为现在 Go 官方尚未在语法层面提供对泛型的支撑,咱们只能经过空接口结合反射来完成。

空接口 interface{} 本身能够表明任何类型的泛型,不过这个泛型太泛了,咱们必须结合反射在运行时对实践传入的参数做类型查看,让其变得可控,

从而保证程序的健壮性,否则很简单因为传递进来的参数类型不合法导致程序溃散。

下面咱们经过一个自定义容器类型的完成来演示怎么根据空接口和反射来完成泛型:

根据 空接口和反射的 容器,完成泛型

 	package main
	import (
	  "fmt"
	  "reflect"
	)

经过传入存储元素类型 和 容量 来初始化 容器

	type Container struct {

	  s reflect.Value

	}

根据切片类型完成的容器,这儿经过反射动态初始化这个底层切片

	func NewContainer(t reflect.Type, size int) *Container {
	  return &Container{s: reflect.MakeSlice(reflect.SliceOf(t), 0, size)}
	}

经过反射对 实践传递来的 元素类型进行运行时查看

假如与容器初始化设置的元素类型不同,则回来错误信息

c.s.Type() 对应的是 切片类型,c.s.Type().Elem()对应的才是切片元素类型

    func (c *Container) Put(val interface{}) error {
  if reflect.ValueOf(val).Type() != c.s.Type().Elem() {

c.s 切片元素类型 与 传入参数不同

    return fmt.Errorf("put error:cannot put a %T into a slice of %s", c.s.Type().Elem())
     }

假如类型查看经过则将其增加到容器

  c.s = reflect.Append(c.s, reflect.ValueOf(val))
  return nil
    }
    func (c *Container) Get(val interface{}) error {

仍是经过反射对元素 类型进行查看,假如不经过则回来错误信息

kind 与 Type 相比范围更大,表明类别,如指针,而Type则对应具体类型,如 *int

因为 val是指针类型,所有需求经过reflect.ValueOf(val).Elem() 获取指针指向的类型

  if reflect.ValueOf(val).Kind() != reflect.Ptr || reflect.ValueOf(val).Elem().Type() != c.s.Type().Elem() {
    return fmt.Errorf("get error:needs *%s but got %T", c.s.Type().Elem(), val)
    }

将容器第一个索引位置值赋值给 val 指针

  reflect.ValueOf(val).Elem().Set(c.s.Index(0))

然后删去容器第一个索引位置值

  c.s = c.s.Slice(1, c.s.Len())
  return nil 
    }
    func main() {
  nums := []int{1, 2, 3, 4, 5}

初始化容器,元素类型和nums中的元素类型相同

  c := NewContainer(reflect.TypeOf(nums[0]), 16)
  for _, n := range nums {
    if err := c.Put(n); err != nil {
      panic(err)
    }

从容器读取元素,将回来成果初始化为0

    num := 0
    if err := c.Get(&num); err != nil {
      panic(err)
    }

打印回来成果值

    fmt.Printf("%v, (%T)\n", num, num)
      }
	  err := c.Put("s")   //put error:cannot put a *reflect.rtype into a slice of %!s(MISSING)
	  err2 := c.Get("s100") //get error:needs *int but got string
	  fmt.Println(err, err2)
	}
	```

具体细节都现已在代码注释中具体标示了,履行上述代码,打印成果如下:

   -> chapter04 git:(main) x go run reflect/generic.go
   1(int)

假如咱们企图增加其他类型元素到容器:

	```
	iferr:=c.Put("s");err!=nil{panic(err)}
	```

或者存储回来成果的变量类型与容器内元素类型不符:

	```
	iferr:=c.Get(num);err!=nil{panic(err)}
	```

都会报错:

   -> generator git:(main) x go run reflect/generic.go
   panic: put error: can not put staring into a slice of int
   goroutine 1 [running]:
   main.main()
      ...
   exit status 2
  -> generator git:(main) x go run reflect/generic.go
   panic: get error: needs *int but got int
   goroutine 1 [running]:
   main.main()
      ...
   exit status 2

在这儿,为了提高程序的健壮性,咱们引入了错误处理机制,这块内容咱们行将在下个章节中具体给大家介绍。

小结

  • 泛型的运用场景

    对特殊函数进行操作的函数

    运用反射时,可能更慢,它不是类型查看
    

    通用数据结构

    切片器数据结构,如链表 和 二叉树
    类型参数 替换 接口 存储数据,可防止类型断言
    

    不同的类型 需求完成一些通用办法时,有 通用办法时 可运用 类型

    许多函数都有相似代码,而不同点仅仅是 类型不同,此时运用 类型参数

  • 运用类型参数的场景的方式

    主张
        func ReadFour(r io.Reader) ([]byte, error)
    不主张的方式
        func ReadFour[T io.Reader](r T) ([]byte, error)
    

敞开生长之旅!这是我参加「日新计划 2 月更文挑战」的第 30 天,点击查看活动概况