在go语言中自定义泛型的变长参数

本文涉及的产品
性能测试 PTS,5000VUM额度
注册配置 MSE Nacos/ZooKeeper,118元/月
云原生网关 MSE Higress,422元/月
简介: 【7月更文挑战第8天】在Go语言中,由于官方1.18以前的版本不支持泛型,可以通过空接口和反射模拟泛型。泛型适用于通用数据结构和函数,虽牺牲了一些性能,但提高了代码复用和类型安全性。

1 简介

本文示例代码展示了一个自定义Container结构,利用反射创建指定类型切片并进行类型检查。
PutGet方法确保元素类型与容器匹配,否则返回错误。
代码在尝试添加不兼容类型或使用错误类型获取元素时会引发错误,体现了使用反射增强程序健壮性的特点。

2 自定义泛型:变长参数支持

在某些场景下,目前依然能使用反射来实现,比如泛型。
因为现在 Go 官方尚未在1.18版本之前的语法层面提供对泛型的支持,我们只能通过空接口结合反射来实现。

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

从而确保程序的健壮性,否则很容易因为传递进来的参数类型不合法导致程序崩溃。

下面我们通过一个自定义容器类型的实现来演示如何基于空接口和反射来实现泛型:

3 接口和容器的例子

基于 空接口和反射的 容器,实现泛型

     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)

如果我们试图添加其他类型元素到容器:

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

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

    ```
    if err := 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

在这里,为了提高程序的健壮性,我们引入了错误处理机制,这块内容我们即将在下个章节中详细给大家介绍。

4 小结

  • 泛型的使用场景

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

    使用反射时,可能更慢,它不是类型检查
    

    通用数据结构

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

    不同的类型 需要实现一些通用方法时,有 通用方法时 可使用 类型

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

    • 使用类型参数的场景的方式建议

         func ReadFour(r io.Reader) ([]byte, error)
      

不建议的方式

         func ReadFour[T io.Reader](r T) ([]byte, error)
目录
相关文章
|
4天前
|
监控 Linux PHP
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
51 20
|
2天前
|
存储 监控 算法
探秘员工泄密行为防线:基于Go语言的布隆过滤器算法解析
在信息爆炸时代,员工泄密行为对企业构成重大威胁。本文聚焦布隆过滤器(Bloom Filter)这一高效数据结构,结合Go语言实现算法,帮助企业识别和预防泄密风险。通过构建正常操作“指纹库”,实时监测员工操作,快速筛查可疑行为。示例代码展示了如何利用布隆过滤器检测异常操作,并提出优化建议,如调整参数、结合日志分析系统等,全方位筑牢企业信息安全防线,守护核心竞争力。
|
10天前
|
Go C语言
Go语言入门:分支结构
本文介绍了Go语言中的条件语句,包括`if...else`、`if...else if`和`switch`结构,并通过多个练习详细解释了它们的用法。`if...else`用于简单的条件判断;`if...else if`处理多条件分支;`switch`则适用于基于不同值的选择逻辑。特别地,文章还介绍了`fallthrough`关键字,用于优化重复代码。通过实例如判断年龄、奇偶数、公交乘车及成绩等级等,帮助读者更好地理解和应用这些结构。
34 14
|
24天前
|
存储 监控 算法
内网监控系统之 Go 语言布隆过滤器算法深度剖析
在数字化时代,内网监控系统对企业和组织的信息安全至关重要。布隆过滤器(Bloom Filter)作为一种高效的数据结构,能够快速判断元素是否存在于集合中,适用于内网监控中的恶意IP和违规域名筛选。本文介绍其原理、优势及Go语言实现,提升系统性能与响应速度,保障信息安全。
27 5
|
9天前
|
监控 关系型数据库 MySQL
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
18 0
|
缓存 NoSQL
golang实现自定义驱动的Cache
近期在写 ActivedRouter项目的时候需求一个缓存模型,要求缓存模型支持不同驱动,例如:memory、file、redis、mysql,实现思路代码如下: cache.go文件,定义缓存对外接口 //ActivedRouter //Author:usher.
1138 0
|
2月前
|
存储 监控 算法
员工上网行为监控中的Go语言算法:布隆过滤器的应用
在信息化高速发展的时代,企业上网行为监管至关重要。布隆过滤器作为一种高效、节省空间的概率性数据结构,适用于大规模URL查询与匹配,是实现精准上网行为管理的理想选择。本文探讨了布隆过滤器的原理及其优缺点,并展示了如何使用Go语言实现该算法,以提升企业网络管理效率和安全性。尽管存在误报等局限性,但合理配置下,布隆过滤器为企业提供了经济有效的解决方案。
95 8
员工上网行为监控中的Go语言算法:布隆过滤器的应用
|
2月前
|
存储 Go 索引
go语言中数组和切片
go语言中数组和切片
48 7
|
2月前
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
2月前
|
程序员 Go
go语言中结构体(Struct)
go语言中结构体(Struct)
124 71