GO泛型相关

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

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] []Tfunc 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]VALfunc 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 stringdata 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   smaxval Tminval 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 Tfor _, v := range m {sum += v}return sum
}func main() {var t mySlice[int] = []int{1, 2, 3, 4, 5}fmt.Println(t.sum()) //15var 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 somethingcase 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 inttype _ 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 的交集AllIntUint
}type B interface { // 接口B代表的类型集是 AllInt 和 ~int 的交集AllInt~int
}

上面这个例子中

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

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

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

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

6.2.4 空集

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

go
复制代码type Bad interface {intfloat32 
} // 类型 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 | ~[]runeRead(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.mzph.cn/news/715561.shtml

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

相关文章

Pake 轻松构建轻量级多端桌面应用

Pake 利用 Rust 轻松构建轻量级多端桌面应用,支持 Mac / Windows / Linux。 小白用户:可以使用 「常用包下载」 方式来体验 Pake 的能力,也可试试 Action 方式。 开发用户:可以使用 「命令行一键打包」,对 Mac 比较友…

Matlab 机器人工具箱 动力学

文章目录 R.dynR.fdynR.accelR.rneR.gravloadR.inertiaR.coriolisR.payload官网:Robotics Toolbox - Peter Corke R.dyn 查看动力学参数 mdl_puma560; p560.dyn;%查看puma560机械臂所有连杆的动力学参数 p560.dyn(2);%查看puma560机械臂第二连杆的动力学参数 p560.links(2)…

react父子组件传参demo

父组件代码 /* eslint-disable next/next/no-img-element */ "use client"; import React, { useEffect, useState } from "react"; import WxTip from ../components/WxTipconst Download () > {const [showTip, setshowTip] useState<boolean…

javaweb day9 day10

昨天序号标错了 vue的组件库Elent 快速入门 写法 常见组件 复制粘贴 打包部署

高斯消元法解线性方程组

高斯消元法 基本性质&#xff1a; 把某一行乘一个非 0 0 0的数 (方程的两边同时乘上一个非 0 0 0数不改变方程的解) 交换某两行 (交换两个方程的位置) 把某行的若干倍加到另一行上去 &#xff08;把一个方程的若干倍加到另一个方程上去&#xff09; 算法步骤 枚举每一列c …

洛谷p1225 c++(使用高精度)

题解: 一开始我这个代码想到的是使用递归来求解 int digui(int n){int sum=0;if(n==1)sum=1;if(n==2)sum=2;if(n==1||n==2)return sum;if(n>2){return sum+=digui(n-1)+digui(n-2);} } 但是后面发现明显超时,我试图用记忆化搜索来抢救一下,所以就有了下面代码 int di…

图论 - DFS深度优先遍历、BFS广度优先遍历、拓扑排序

文章目录 前言Part 1&#xff1a;DFS&#xff08;深度优先遍历&#xff09;一、排列数字1.题目描述输入格式输出格式数据范围输入样例输出样例 2.算法 二、n皇后问题1.问题描述输入格式输出格式数据范围输入样例输出样例 2.算法 三、树的重心1.问题描述输入格式输出格式数据范围…

计算机二级Python刷题笔记------基本操作题23、33、35、37(考察字符串)

文章目录 第二十三题&#xff08;字符串替换&#xff1a;replace(old,new)&#xff09;第三十三题&#xff08;字符串遍历&#xff09;第三十五题&#xff08;字符串与列表&#xff09;第三十七题&#xff08;拼接字符串&#xff09; 第二十三题&#xff08;字符串替换&#xf…

第19章-IPv6基础

1. IPv4的缺陷 2. IPv6的优势 3. 地址格式 3.1 格式 3.2 长度 4. 地址书写压缩 4.1 段内前导0压缩 4.2 全0段压缩 4.3 例子1 4.4 例子 5. 网段划分 5.1 前缀 5.2 接口标识符 5.3 前缀长度 5.4 地址规模分类 6. 地址分类 6.1 单播地址 6.2 组播地址 6.3 任播地址 6.4 例子 …

Redis学习------实战篇----2024/02/29----缓存穿透,雪崩,击穿

1.缓存穿透 Overridepublic Result queryById(Long id) {//1.从redis中查询缓存String key CACHE_SHOP_KEY id;String shopJson stringRedisTemplate.opsForValue().get(key);//2.判断是否存在//3.存在则直接返回if (StrUtil.isNotBlank(shopJson)){Shop shop JSONUtil.toB…

每日一题 2867统计树中的合法路径

2867. 统计树中的合法路径数目 题目描述&#xff1a; 给你一棵 n 个节点的无向树&#xff0c;节点编号为 1 到 n 。给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges &#xff0c;其中 edges[i] [ui, vi] 表示节点 ui 和 vi 在树中有一条边。 请你返回树中的 合法路…

Vins-Moon配准运行

Vins-Moon运行 源码地址电脑配置环境配置编译适配Kitti数据集运行结果Euroc数据集kitti数据集 evo评估&#xff08;KITTI数据&#xff09;输出轨迹(tum格式)结果 源码地址 源码链接&#xff1a;https://github.com/HKUST-Aerial-Robotics/VINS-Mono.git 电脑配置 Ubuntu 18.…

破解SQL Server迷局,彻底解决“管道的另一端无任何进程错误233”

问题描述&#xff1a;在使用 SQL Server 2014的时候&#xff0c;想用 SQL Server 身份方式登录 SQL Servcer Manager&#xff0c;结果报错&#xff1a; 此错误消息&#xff1a;表示SQL Server未侦听共享内存或命名管道协议。 问题原因&#xff1a;此问题的原因有多种可能 管道…

人才测评系统在企业中的作用有哪些?

一个企业除了产出价值给社会&#xff0c;它还有自己的工作架构体系&#xff0c;无论的工作时间制度上&#xff0c;还是工资组成方向&#xff0c;这样公司才能正常运转&#xff0c;那么人才测评系统可以在企业中充当一个什么角色呢&#xff1f;又或者说它起着什么作用呢&#xf…

走进SQL审计视图——《OceanBase诊断系列》之二

1. 前言 在SQL性能诊断上&#xff0c;OceanBase有一个非常实用的功能 —— SQL审计视图(gv$sql_audit)。在OceanBase 4.0.0及更高版本中&#xff0c;该功能是 gv$ob_sql_audit。它可以使开发和运维人员更方便地排查在OceanBase上运行过的任意一条SQL&#xff0c;无论这些SQL是成…

kafka文件存储机制和消费者

1.broker文件存储机制 去查看真正的存储文件&#xff1a; 在/opt/module/kafka/datas/ 路径下 kafka-run-class.sh kafka.tools.DumpLogSegments --files ./00000000000000000000.index 如果是6415那么这个会存储在563的log文件之中&#xff0c;因为介于6410和10090之间。 2.…

java mysql八股

mysql中如何定位慢查询 表象&#xff1a;页面加载过慢、接口压测响应时间较长&#xff08;超过1秒&#xff09; 可以采用开源工具如Arthas以及Skywalking&#xff0c;使用skywalking可以检测出哪个接口过慢。同时可以在mysql中开启慢日志查询&#xff0c;设置值为2秒&#xff0…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的行人车辆检测与计数(Python+PySide6界面+训练代码)

摘要&#xff1a;开发行人车辆检测与计数系统对于提升城市交通管理和监控系统的效率至关重要。本篇博客详细介绍了如何利用深度学习构建一个行人车辆检测与计数系统&#xff0c;并提供了完整的实现代码。该系统基于强大的YOLOv8算法&#xff0c;并结合了YOLOv7、YOLOv6、YOLOv5…

[Java 探索者之路] 一个大厂都在用的分布式任务调度平台

分布式任务调度平台是一种能够在分布式计算环境中调度和管理任务的系统&#xff0c;在此环境下&#xff0c;各个任务可以在独立的节点上运行。它有助于提升资源利用率&#xff0c;增强系统扩展性以及提高系统对错误的容忍度。 文章目录 1. 分布式任务调度平台1. 基本概念1.1 任…

Java基于SpringBoot的旅游网站的设计与实现论文

目 录 摘 要 2 Abstract 3 1.1 课题开发的背景 4 1.2 课题研究的意义 4 1.3 研究内容 5 第二章 系统开发关键技术 6 2.1 JSP技术介绍 6 2.2 JAVA简介 6 2.3 MyEclipse开发环境 7 2.4 Tomcat服务器 7 2.5 Spring Boot框架 7 2.6 MySQL数据库 8 第三章 系统分析 9 3.1 系统可行性…