GO泛型相关

news2024/9/25 13:16:27

通过引入 类型形参类型实参 这两个概念,我们让一个函数获得了处理多种不同类型数据的能力,这种编程方式被称为 泛型编程。

2. Go的泛型

  • 类型形参 (Type parameter)
  • 类型实参(Type argument)
  • 类型形参列表( Type parameter list)
  • 类型约束(Type constraint)
  • 实例化(Instantiations)
  • 泛型类型(Generic type)
  • 泛型接收器(Generic receiver)
  • 泛型函数(Generic function)

基本格式:

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

泛型类型不能直接拿来使用,必须传入类型实参(Type argument) 将其确定为具体的类型之后才可使用。而传入类型实参确定具体类型的操作被称为 实例化(Instantiations)

// 声明一个泛型
type slice[T int | float32 | float64] []T

func main() {
	// 这里传入了类型实参int,泛型类型Slice[T]被实例化为具体的类型 Slice[int]
	var a slice[int] = []int{1, 2, 3}
	fmt.Println(a) //[1 2 3]
    // 传入类型实参float32, 将泛型类型Slice[T]实例化为具体的类型 Slice[float32]
	var b slice[float32] = []float32{1.2, 123.123, 2123.1}
	fmt.Println(b) //[1.2 123.123 2123.1]
}

其他类型的泛型


//泛型map
type myMap[KEP int | string, VAL int | float32] map[KEP]VAL

func main() {
	table := myMap[string, int]{
		"xiaoming": 190,
		"xiaohong": 150,
	}

	fmt.Printf("%+v", table)  //map[xiaohong:150 xiaoming:190]
}
//泛型结构体
type myStruct[T int|string] struct {
	name string
	data T
}
// 一个泛型接口(关于泛型接口在后半部分会详细讲解)
type IPrintData[T int | float32 | string] interface {
    Print(data T)
}
// 一个泛型通道,可用类型实参 int 或 string 实例化
type MyChan[T int | string] chan T

3 类型形参的互相套用

// 泛型嵌套
type woStruct[T int | string, s []T] struct {
	Data   s
	maxval T
	minval T
}

func main() {
	var test woStruct[int, []int] = woStruct[int, []int]{
		[]int{1, 2, 3},
		12,
		31,
	}
	fmt.Printf("%+v", test)  //{Data:[1 2 3] maxval:12 minval:31}
}

任何泛型类型都必须传入类型实参实例化才可以使用。上面的代码中,我们为T传入了实参 int,然后因为 S 的定义是 []T ,所以 S 的实参自然是 []int

因为 S 的定义是 []T ,所以 T 一定决定了的话 S 的实参就不能随便乱传了

几种语法错误

  • 定义泛型类型的时候,基础类型不能只有类型形参,如下:
  • 当类型约束的一些写法会被编译器误认为是表达式时会报错。如下:
go
复制代码// 错误,类型形参不能单独使用
type CommonType[T int|string|float32] T
go
复制代码//✗ 错误。T *int会被编译器误认为是表达式 T乘以int,而不是int指针
type NewType[T *int] []T
// 上面代码再编译器眼中:它认为你要定义一个存放切片的数组,数组长度由 T 乘以 int 计算得到
type NewType [T * int][]T 

//✗ 错误。和上面一样,这里不光*被会认为是乘号,| 还会被认为是按位或操作
type NewType2[T *int|*float64] []T 

//✗ 错误
type NewType2 [T (int)] []T

为了避免这种误解,解决办法就是给类型约束包上 interface{} 或加上逗号消除歧义(关于接口具体的用法会在后半篇提及)

go
复制代码type NewType[T interface{*int}] []T
type NewType2[T interface{*int|*float64}] []T 

// 如果类型约束中只有一个类型,可以添加个逗号消除歧义
type NewType3[T *int,] []T

//✗ 错误。如果类型约束不止一个类型,加逗号是不行的
type NewType4[T *int|*float32,] []T

因为上面逗号的用法限制比较大,这里推荐统一用 interface{} 解决问题

匿名结构体不支持泛型

4. 泛型receiver


type mySlice[T int | string | float32] []T

//泛型方法
func (m mySlice[T]) sum() T {
	var sum T
	for _, v := range m {
		sum += v
	}
	return sum
}

func main() {
	var t mySlice[int] = []int{1, 2, 3, 4, 5}
	fmt.Println(t.sum()) //15

	var f mySlice[float32] = []float32{1.2, 3.4, 5.6}
	fmt.Println(f.sum())  //10.200001
 
}
  • 首先看receiver (s MySlice[T]) ,所以我们直接把类型名称 MySlice[T] 写入了receiver中
  • 然后方法的返回参数我们使用了类型形参 T ****(实际上如果有需要的话,方法的接收参数也可以实用类型形参)
  • 在方法的定义中,我们也可以使用类型形参 T (在这个例子里,我们通过 var sum T 定义了一个新的变量 sum )

动态判断变量的类型

泛型不像接口一样可以通过类型断言来判断其类型

但可以通过反射来实现动态判断其类型

func (receiver Queue[T]) Put(value T) {
    // Printf() 可输出变量value的类型(底层就是通过反射实现的)
    fmt.Printf("%T", value) 

    // 通过反射可以动态获得变量value的类型从而分情况处理
    v := reflect.ValueOf(value)

    switch v.Kind() {
    case reflect.Int:
        // do something
    case reflect.String:
        // do something
    }

    // ...
}

泛型函数

func main() {
	//在调用函数的时候声明类型
	fmt.Println(add[float64](float64(6.123), float64(8.12312)))
	//自动类型推断
	fmt.Println(add(19, 123)) //142
}
//定义一个函数泛型
func add[T int | float32 | float64](a T, b T) T {
	return a + b
}

匿名函数不能自己定义类型形参:

但是匿名函数可以使用别处定义好的类型实参,如:

go
复制代码func MyFunc[T int | float32 | float64](a, b T) {

    // 匿名函数可使用已经定义好的类型形参
    fn2 := func(i T, j T) T {
        return i*2 - j*2
    }

    fn2(a, b)
}


 

既然函数都支持泛型了,那你应该自然会想到,方法支不支持泛型?很不幸,目前Go的方法并不支持泛型

6. 变得复杂的接口

type IntUintFloat interface {
    int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}

type Slice[T IntUintFloat] []T

这段代码把类型约束给单独拿出来,写入了接口类型 IntUintFloat 当中。需要指定类型约束的时候直接使用接口 IntUintFloat 即可。

不过这样的代码依旧不好维

6.1 ~ : 指定底层类型

上面定义的 Slie[T] 虽然可以达到目的,但是有一个缺点:

go
复制代码var s1 Slice[int] // 正确 

type MyInt int
var s2 Slice[MyInt] // ✗ 错误。MyInt类型底层类型是int但并不是int类型,不符合 Slice[T] 的类型约束

这里发生错误的原因是,泛型类型 Slice[T] 允许的是 int 作为类型实参,而不是 MyInt (虽然 MyInt 类型底层类型是 int ,但它依旧不是 int 类型)。

为了从根本上解决这个问题,Go新增了一个符号 ~ ,在类型约束中使用类似 ~int 这种写法的话,就代表着不光是 int ,所有以 int 为底层类型的类型也都可用于实例化。

限制:使用 ~ 时有一定的限制:

  1. ~后面的类型不能为接口
  2. ~后面的类型必须为基本类型
type MyInt int

type _ interface {
    ~[]byte  // 正确
    ~MyInt   // 错误,~后的类型必须为基本类型
    ~error   // 错误,~后的类型不能为接口
}

6.2 从方法集(Method set)到类型集(Type set)

当满足以下条件时,我们可以说 类型 T 实现了接口 I ( type T implements interface I)

  • T 不是接口时:类型 T 是接口 I 代表的类型集中的一个成员 (T is an element of the type set of I)
  • T 是接口时: T 接口代表的类型集是 I 代表的类型集的子集(Type set of T is a subset of the type set of I)

6.2.2 类型的并集

并集我们已经很熟悉了,之前一直使用的 | 符号就是求类型的并集( union )

6.2.3 类型的交集

接口可以不止书写一行,如果一个接口有多行类型定义,那么取它们之间的 交集

go
复制代码type AllInt interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint32
}

type Uint interface {
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}

type A interface { // 接口A代表的类型集是 AllInt 和 Uint 的交集
    AllInt
    Uint
}

type B interface { // 接口B代表的类型集是 AllInt 和 ~int 的交集
    AllInt
    ~int
}

上面这个例子中

  • 接口 A 代表的是 AllInt 与 Uint 的 交集,即 ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
  • 接口 B 代表的则是 AllInt 和 ~int 的交集,即 ~int

除了上面的交集,下面也是一种交集:

go
复制代码type C interface {
    ~int
    int
}

很显然,~int 和 int 的交集只有int一种类型,所以接口C代表的类型集中只有int一种类型
 

6.2.4 空集

当多个类型的交集如下面 Bad 这样为空的时候, Bad 这个接口代表的类型集为一个空集

go
复制代码type Bad interface {
    int
    float32 
} // 类型 int 和 float32 没有相交的类型,所以接口 Bad 代表的类型集为空


 

6.2.5 空接口和 any

上面说了空集,接下来说一个特殊的类型集——空接口 interface{} 。因为,Go1.18开始接口的定义发生了改变,所以 interface{} 的定义也发生了一些变更:

空接口代表了所有类型的集合

// 空接口代表所有类型的集合。写入类型约束意味着所有类型都可拿来做类型实参
type Slice[T interface{}] []T

因为空接口是一个包含了所有类型的类型集,所以我们经常会用到它。于是,Go1.18开始提供了一个和空接口 interface{} 等价的新关键词 any ,用来使代码更简单:

type Slice[T any] []T // 代码等价于 type Slice[T interface{}] []T


 

6.2.6 comparable(可比较) 和 可排序(ordered)

Go直接内置了一个叫 comparable 的接口,它代表了所有可用 != 以及 == 对比的类型

6.3.1 基本接口(Basic interface)

接口定义中如果只有方法的话,那么这种接口被称为基本接口(Basic interface)。这种接口就是Go1.18之前的接口,用法也基本和Go1.18之前保持一致

6.3.2 一般接口(General interface)

如果接口内不光只有方法,还有类型的话,这种接口被称为 一般接口(General interface) ,如下例子都是一般接口:

go
复制代码type Uint interface { // 接口 Uint 中有类型,所以是一般接口
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}

type ReadWriter interface {  // ReadWriter 接口既有方法也有类型,所以是一般接口
    ~string | ~[]rune

    Read(p []byte) (n int, err error)
    Write(p []byte) (n int, err error)
}

一般接口类型不能用来定义变量,只能用于泛型的类型约束中。所以以下的用法是错误的:

go
复制代码type Uint interface {
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}

var uintInf Uint // 错误。Uint是一般接口,只能用于类型约束,不得用于变量定义

这一限制保证了一般接口的使用被限定在了泛型之中,不会影响到Go1.18之前的代码,同时也极大减少了书写代码时的心智负担


 


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1486698.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【IEEEE会议征稿】第六届下一代数据驱动网络国际学术会议(NGDN 2024)

第六届下一代数据驱动网络国际学术会议(NGDN 2024) The Sixth International Conference on Next Generation Data-driven Networks 基于前几届在英国埃克塞特 (ISPA 2020) 、中国沈阳 (TrustCom 2021) 和中国武汉(IEEETrustCom-2022)成功举办的经验&a…

048 异常

什么是异常 异常体系结构 异常的继承关系 Error Exception 异常处理机制 try:用{}将可能产生异常的代码包裹catch:与try搭配使用,捕获try包裹代码中抛出的异常并进行后续动作finally:跟在try后,在try和catch之后执行…

使用 Grafana 使用JSON API 请求本地接口 报错 bad gateway(502)解决

一 . 问题: 在用docker部署Grafana 来实现仪表盘的展示,使用到比较多的就是使用JAON API插件调用本地部署的API,比如访问localhost下的 /test_data 接口,一般我们使用的是http://localhost:8080/test_data, 但是在访…

java 使用easyui开发导出excle打开错误

解决: contentType 加字符编码 ;charsetutf-8 就好了

[AutoSar]BSW_Com08 CAN driver 模块介绍及参数配置说明 (一)

目录 关键词平台说明一、缩写和定义二、CAN driver 所在位置三、CAN 模块的主要功能四、功能规格4.1 Driver State Machine4.2 CAN控制器状态机4.3 CAN控制器状态机转换4.3.1 调用function Can_Init 导致的状态转换4.3.2 调用Can_ChangeBaudrate导致的状态转换4.3.3 调用Can_Se…

无穷积分例子

以下几个题容易出错,特意记录一下。 判断积分式的敛散性 ∫ − ∞ ∞ 1 x 2 e 1 x d x \int _{-\infty } ^ {\infty} \frac{1}{x^2} e ^{\frac{1}{x}} dx ∫−∞∞​x21​ex1​dx 要注意瑕点0的处理。无穷积分,一般将积分域按瑕点拆分并分别积分。 判断…

谷粒商城【成神路】-【8】——商品上架

目录 1.数据模型封装 1.es数据模型 2.将es数据模型封装为JAVA bean 3.根据前端发送请求,编写controller 2.模型实现 2.1服务controller 2.2服务service 2.3服务远程调用接口 2.4检索服务controller 2.5检索服务保存到es 2.6库存查询服务 1.数据模型封装 1.es数据模…

Linux多线程服务端编程:使用muduo C++网络库 学习笔记 附录D 关于TCP并发连接的几个思考题与试验

前几天作者在新浪微博上出了两道有关TCP的思考题,引发了一场讨论(http://weibo.com/1701018393/eCuxDrtaONn)。 第一道初级题目是:有一台机器,它有一个IP,上面运行了一个TCP服务程序,程序只侦听…

STM32学习7 按键扫描

STM32学习7 按键扫描 一、实验电路介绍二、按键GPIO初始化三、扫描原理1. GPIO引脚配置2. 状态轮询3. 按键状态检测4. 循环扫描的优缺点优点:缺点: 四、一次扫描与持续扫描五、代码实现1. 头文件定义2. 函数实现3. 主体函数 一、实验电路介绍 本实验使用…

代码随想录算法训练营第七天

● 自己看到题目的第一想法 第454题.四数相加II 方法&#xff1a; 方法一&#xff1a; 暴力法 思路&#xff1a; 注意&#xff1a; 代码&#xff1a; class Solution { public:int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<i…

ABB眼中AI推动机器人创新的三大方向

文 | BFT机器人 ABB的历史是一部充满革新与进步的史诗&#xff0c;它的机器人篇章始于1974年&#xff0c;那一年它向世界推出了被誉为“全球首个商用全电动机器人”的IRB 6。时隔半个世纪&#xff0c;ABB的机器人产品线已变得无比强大和多元&#xff0c;囊括了先进的工业机械臂…

OpenAI 与开源多语言嵌入模型

原文地址&#xff1a;OpenAI vs Open-Source Multilingual Embedding Models 选择最适合您的数据的模型 2024 年 2 月 25 日 OpenAI最近发布了他们的新一代embedding模型&#xff0c;称为embeddingv3&#xff0c;他们描述是他们性能最好的embedding模型&#xff0c;具有更高…

MySQL8安装切换密码验证方式

一、MySQL8中新增了一种密码验证方式&#xff1a;caching_sha2_password&#xff0c;如果安装时选择了如下方式&#xff1a; 则数据库使用新的caching_sha2_password密码验证方式。 二、如果安装时选择了caching_sha2_password验证方式&#xff0c;而安装后想发回传统的mysql_…

云原生之容器编排实践-ruoyi-cloud项目部署到K8S:Redis7.2.3

背景 前面搭建好了 Kubernetes 集群与私有镜像仓库&#xff0c;终于要进入服务编排的实践环节了。本系列拿 ruoyi-cloud 项目进行练手&#xff0c;按照 MySQL &#xff0c; Nacos &#xff0c; Redis &#xff0c; Nginx &#xff0c; Gateway &#xff0c; Auth &#xff0c;…

Power BI vs Superset BI 调研报告

调研结论 SupersetPower BI价格开源①. Power BI Pro 每人 $10/月($120/年/人) ②. Power BI Premium 每人 $20/月($240/年/人) ③. Power BI Embedded:4C10G $11W/年 权限基于角色的访问控制,支持细粒度的访问: 表级别、库级别、图表级别,看板级别,用户级别 基于角色…

VS Code 的粘性滚动预览 - 类似于 Excel 的冻结首行

VS Code 的粘性滚动预览 - 类似于 Excel 的冻结首行功能&#xff0c;即滚动 UI 显示当前源代码范围。便于在代码行数比较多的时候更好的知道自己所在的位置。粘性滚动UI 显示用户在滚动期间所处的范围&#xff0c;将显示编辑器顶部所在的类/接口/命名空间/函数/方法/构造函数&a…

JavaScript之引用类型

系列文章目录 文章目录 系列文章目录前言一、Object类型二、Array类型三、Date类型四、Function类型五、内置对象 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文…

C++ opencv 学习

文章目录 1、创建窗口2、读取图片3、视频采集4、Mat的使用5、异或操作6、通道分离&#xff0c;通道合并7、色彩空间转换8、最大值、最小值9、绘制图像10、多边形绘制11、随机数12、鼠标实时绘制矩形13、归一化14、resize操作15、旋转翻转16、视频操作17、模糊操作18、高斯模糊操…

ONLYOFFICE文档8.0全新发布:私有部署、卓越安全的协同办公解决方案

ONLYOFFICE文档8.0全新发布&#xff1a;私有部署、卓越安全的协同办公解决方案 文章目录 ONLYOFFICE文档8.0全新发布&#xff1a;私有部署、卓越安全的协同办公解决方案摘要&#x1f4d1;引言 &#x1f31f;正文&#x1f4da;一、ONLYOFFICE文档概述 &#x1f4ca;二、ONLYOFFI…

字符串之manacher(马拉车)算法

这个算法用途就是查找字符串内的最长回文串 正常情况下&#xff0c;我们查找回文序列&#xff0c;会去用双指针比较&#xff0c;这样的话数据大的时候&#xff0c;时间复杂度就上去了&#xff0c;其实这个马拉车算法和kmp算法的一部分是有些相像的&#xff0c;建议先看我的上篇…