Go 中的泛型,日常如何使用

泛型从 go 的 1.18 开始支持

什么是泛型编程

在泛型出现之前,如果需要计算两数之和,可能会这样写:

func Add(a, b int) int {returb a + b
}

这个很简单,但是只能两个参数都是 int 类型的时候才能调用

如果想要计算两个浮点数的和,就需要再定义一个函数

func AddFloat32(a, b float32) float32 {return a + b
}

如果需要计算两个字符串的和,就需要再定义一个函数,这样太过麻烦

在一个函数中,行参只是类似占位符的东西,只有调用函数传入实参之后才有具体的值

如果把行参、实参的概念推广一下,给变量的类型也引入类似行参实参的概念,那就可以接收各种类型的值

函数就类似于:

func Add(a, b T) T {return a + b
}

在这段代码中,T 可以称为 类型行参(type parameter),它不是具体的类型,在定义函数时参数的类型不确定,在传入时才确定,传入参数的具体类型称为 类型实参(type argument)

这样通过 类型行参类型实参 进行编码的方式就称为 泛型编程

Go 的泛型

除了上面提到的类型行参和类型实参,Go 还引入了其他概念:

  • 类型形参 (Type parameter)

  • 类型实参(Type argument)

  • 类型形参列表( Type parameter list)

  • 类型约束(Type constraint)

  • 实例化(Instantiations)

  • 泛型类型(Generic type)

  • 泛型接收器(Generic receiver)

  • 泛型函数(Generic function)

一个一个说

类型行参、类型实参、类型约束和泛型类型

假如现在要定义一个切片,可以容纳 int、float32、string 等多种类型,不使用泛型常规做法是为每种类型各自定义一个切片

使用泛型,就可以这样定义:

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

在这行代码中:

  • T 就是上面介绍的 类型参数,在定义 slice 时 T 的类型不确定,类似于一个占位符

  • int|float|string 称为 类型约束,中间的 | 就是告诉编译器只能接收这几种类型中的一种

  • 中括号[] 中的 T int|float32|float64 这一整串定义了所有的类型实参(这里只有 T 这一个类型行参),称为 类型行参列表

泛型类型不能拿来使用,必须传入类型实参,能确定类型后才能使用,这个过程称为 实例化

func main() {type AllTypeSlice[T int | float32 | string] []T
​intSlice := AllTypeSlice[int]{}fmt.Printf("%T\n", intSlice) // main.AllTypeSlice[int]
​floatSlice := AllTypeSlice[float32]{}fmt.Printf("%T\n", floatSlice) // main.AllTypeSlice[float32]
}

类型行参的个数可以有多个,比如:

type AllTypeMap[KEY int | string, VALUE float32 | float64] map[KEY]VALUE

这样就定义了一个泛型 map,key 和 value 都可以是多种

func main() {type AllTypeMap[KEY int | string, VALUE float32 | float64] map[KEY]VALUE
​var a AllTypeMap[string, float64] = map[string]float64{"jack_score": 9.6,"bob_score":  8.4,}fmt.Printf("%T\n", a) // main.AllTypeMap[string,float64]
}
其他的泛型类型

所有类型定义都可以使用类型行参,包括结构体、接口、通道

// 泛型结构体
type AllTypeStruct[T int | string] struct {Name stringData T
}
​
// 泛型接口
type PrintData[T int | float32] interface {Print(data T)
}
​
// 泛型通道
type AllTypeChan[T int | string] chan T
类型行参的嵌套

类型行参是可以嵌套使用的,比如:

type AllTypeStruct[T int | float32, S []T] struct {Data     SMaxValue TMinValue T
}

使用:

s1 := AllTypeStruct[int, []int]{}
s2 := AllTypeStruct[int, []float32]{} // 报错 类型需要一致

即使 T 可以从 int 和 float32 中选,但是传入时类型已经确定了,所以 T 的类型和 []T 中 T 的类型要保持一致

泛型的使用

泛型一般用来实现一些不需要关注具体类型就可以使用的通用方法

给出一个比较常用的场景:Gorm 中使用泛型写一些基础方法,不同类型调用这些方法,会使用对应的 model,通过相同的逻辑查不同的表,而不需要再分别写各自的方法

常见的一些通用方法:

func IsErrRecordNotFound(err error) bool {return errors.Is(err, gorm.ErrRecordNotFound)
}
​
type baseRepo[T any] struct {db *gorm.DB
}
​
func NewBaseRepo[T any](db *gorm.DB) baseRepo[T] {return baseRepo[T]{db: db}
}
​
func (r *baseRepo[T]) GetDB() *gorm.DB {return r.db
}
​
// GetByID 通过 id 查单条记录
func (r *baseRepo[T]) GetByID(id int, preloads ...string) (*T, error) {var result Tdb := r.dbfor _, preload := range preloads {db = db.Preload(preload)}if err := db.First(&result, id).Error; err != nil {return nil, err}return &result, nil
}
​
// GetByIds 通过 ids 用 IN 查结果集
func (r *baseRepo[T]) GetByIds(ids []int, preloads ...string) (list []*T, err error) {db := r.dbfor _, preload := range preloads {db = db.Preload(preload)}err = db.Unscoped().Where("id IN ?", ids).Find(&list).Errorreturn
}
​
// GetFirst 根据条件查第一条记录 传入对应类型结构体
func (r *baseRepo[T]) GetFirst(cond T, preloads ...string) (*T, error) {var result Tdb := r.dbfor _, preload := range preloads {db = db.Preload(preload)}if err := db.First(&result, cond).Error; err != nil {if IsErrRecordNotFound(err) {return nil, nil}return nil, err}return &result, nil
}
​
// GetList 通过条件查所有记录
func (r *baseRepo[T]) GetList(cond T, preloads ...string) ([]*T, error) {var list []*Tdb := r.dbfor _, preload := range preloads {db = db.Preload(preload)}if err := db.Find(&list, cond).Error; err != nil {return nil, err}return list, nil
}
​
// GetListWithOrder 根据条件查询所有记录并排序
func (r *baseRepo[T]) GetListWithOrder(cond T, order string, preloads ...string) ([]*T, error) {var list []*Tdb := r.dbfor _, preload := range preloads {db = db.Preload(preload)}if err := db.Order(order).Find(&list, cond).Error; err != nil {return nil, err}return list, nil
}
​
// GetPage 分页查询记录
func (r *baseRepo[T]) GetPage(cond *T, order string, pageNo, pageSize int, preloads ...string) ([]*T, error) {db := r.dbfor _, preload := range preloads {db = db.Preload(preload)}
​offset := (pageNo - 1) * pageSizelimit := pageSizedb = db.Order(order).Offset(offset).Limit(limit)if cond != nil {db = db.Where(cond)}
​var list []*Tif err := db.Find(&list).Error; err != nil {return nil, err}return list, nil
}
​
// LikeWithOrder 模糊查询并排序
func (r *baseRepo[T]) LikeWithOrder(columns []string, keyword, order string, preloads ...string) ([]*T, error) {var list []*Tdb := r.dbfor _, preload := range preloads {db = db.Preload(preload)}like := "%" + keyword + "%"for _, column := range columns {query := fmt.Sprintf("%s like ?", column)db = db.Or(query, like)}if err := db.Order(order).Find(&list).Error; err != nil {return nil, err}return list, nil
}
​
// GetAll 查询所有记录
func (r *baseRepo[T]) GetAll(preloads ...string) ([]*T, error) {var list []*Tdb := r.dbfor _, preload := range preloads {db = db.Preload(preload)}if err := db.Find(&list).Error; err != nil {return nil, err}return list, nil
}
​
// GetIds 通过条件查 ids
func (r *baseRepo[T]) GetIds(cond T) ([]int, error) {var ids []intmodel := new(T)if err := r.db.Model(model).Where(cond).Pluck("id", &ids).Error; err != nil {return nil, err}return ids, nil
}
​
// UpdateById 更新单个属性
func (r *baseRepo[T]) UpdateById(id int, column string, value any) error {m := new(T)return r.db.Model(m).Where("id = ?", id).Update(column, value).Error
}
​
// UpdatesById 更新多个属性值,可以传 map 或者结构体
func (r *baseRepo[T]) UpdatesById(id int, updateInfo interface{}) error {m := new(T)return r.db.Model(m).Where("id = ?", id).Updates(updateInfo).Error
}
​
// DeleteByID 删除一条记录
func (r *baseRepo[T]) DeleteByID(id int, force ...bool) error {m := new(T)session := r.db.Model(m)if len(force) > 0 && force[0] == true {session = session.Unscoped()}return session.Delete(m, id).Error
}
​
// DeleteBatch 删除多条记录
func (r *baseRepo[T]) DeleteBatch(ids []int, force ...bool) error {m := new(T)session := r.db.Where("id IN ?", ids)if len(force) > 0 && force[0] == true {session = session.Unscoped()}return session.Delete(m).Error
}
​
// DeleteWith 通过条件删除记录
func (r *baseRepo[T]) DeleteWith(cond T, force ...bool) error {m := new(T)session := r.db.Where(cond)if len(force) > 0 && force[0] == true {session = session.Unscoped()}return session.Delete(m).Error
}
​
// Count 查询记录条目数
func (r *baseRepo[T]) Count(cond T) (int, error) {var num int64m := new(T)err := r.db.Model(m).Where(cond).Count(&num).Errorreturn int(num), err
}

这些方法都是一些逻辑比较简单的通用方法,在使用时,只需要通过结构体嵌套的方式注入 baseRepo ,在查询时就可以直接使用,很方便

结语

对于泛型,我认为只需要掌握基础的使用方法,一些细节和高级用法需要用到时再去查询比较合适,重点在于日常使用

参考(真的很详细,推荐阅读):后端 - Go 1.18 泛型全面讲解:一篇讲清泛型的全部 - 个人文章 - SegmentFault 思否

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

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

相关文章

年度目标5w浏览量达成

目录 前言:目标展示:达成展示: 前言: 去年定了一个目标,今年实现了,以后继续加油,争取2025可以获得15w的阅览量,3000的粉丝数量。 目标展示: 达成展示:

【Python TensorFlow】进阶指南(续篇一)

在前两篇文章中,我们介绍了TensorFlow的基础知识及其在实际应用中的初步使用,并探讨了更高级的功能和技术细节。本篇将继续深入探讨TensorFlow的高级应用,包括但不限于模型压缩、模型融合、迁移学习、强化学习等领域,帮助读者进一…

你不得不知的几种常见的向量数据库产品

产品介绍 在使用 LLM(大型语言模型)知识库时,经常会用到以下几种向量数据库: Milvus:这是一款开源的向量数据库,具有高度可扩展性和高性能。它支持多种向量相似性搜索算法,适用于大规模数据处理…

企业IT架构转型之道:阿里巴巴中台战略思想与架构实战感想

文章目录 第一章:数据库水平扩展第二章:中台战略第三章:阿里分布式服务架构HSF(high speed Framework)、早期Dubbo第四章:共享服务中心建设原则第五章:数据拆分实现数据库能力线性扩展第六章&am…

征程 6 工具链性能分析与优化 2|模型性能优化建议

01 引言 为了应对低、中、高阶智驾场景,以及当前 AI 模型在工业界的应用趋势,地平线推出了征程 6 系列芯片。 在软硬件架构方面,征程 6 不仅保持了对传统 CNN 网络的高效支持能力,还强化了对 Transformer 类型网络的支持&#xf…

字符编码和字符集

1. 字符编码和字符集 1.1. 字符编码 编码:字符 –>字节解码:字节 –>字符字符编码Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。 1.2. 字符集 字符集 Charset:是一个系统支持的所有字符的集合&#xff0…

Kafka面试题解答(二)

1.怎么尽可能保证 Kafka 的可靠性 kafka是可能会出现数据丢失问题的,Leader维护了一个动态的in-sync replica set(ISR),意为和 Leader保持同步的FollowerLeader集合(leader:0,isr:0,1,2)。 如果Follower长…

Chromium127编译指南 Linux篇 - 获取Chromium源码(四)

引言 在前一节中,我们成功配置了 depot_tools 并验证了开发环境的基本可用性。接下来,我们将着手于拉取和初始设置 Chromium 的源码,这是进行 Chromium 开发的首要步骤。通过有效的源码管理和版本控制,我们能更高效、流畅地进行开…

LINUX离线安装Milvus

一.下载安装包 离线安装Docker需要你提前下载Docker的安装包,并将其传输到目标机器上进行安装。以下是一个基于Linux系统的离线安装Docker的简要步骤和示例: 从有网络的机器上下载Docker安装包。 将下载的安装包拷贝到离线的服务器上。 在离线的服务…

【HGT】文献精讲:Heterogeneous Graph Transformer

【HGT】文献精讲:Heterogeneous Graph Transformer 标题: Heterogeneous Graph Transformer (异构图Transformer) 作者团队: 加利福尼亚大学Yizhou Sun 摘要: 近年来,图神经网络(GN…

书客、柏曼、爱德华护眼台灯护眼效果怎么样?真实测评告诉你真相

现在的孩子学习压力很大,在学校课程已经塞满了大半天,课后的作业更是不少,空闲时间还需要去课后补习班的数不胜数。用眼的次数非常的高,眼睛很容易感到疲惫,这时候我们的护眼台灯大有作用,好的护眼台灯可以…

(一)<江科大STM32>——软件环境搭建+新建工程步骤

一、软件环境搭建 (1)安装 Keil5 MDK 文件路径:江科大stm32入门教程资料/Keil5 MDK/MDK524a.EXE,安装即可,路径不能有中文。 (2)安装器件支持包 文件路径:江科大stm32入门教程资料…

Springboot 整合 Java DL4J 打造文本摘要生成系统

🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,…

外排序之文件归并排序实现

1. 外排序 外排序(External sorting)是指能够处理极⼤量数据的排序算法。通常来说,外排序处理的数据不能 ⼀次装⼊内存,只能放在读写较慢的外存储器(通常是硬盘)上。外排序通常采⽤的是⼀种“排序-归并”的策略。在排序阶段&…

校园官网练习---web

HTML&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>西安工商学院</title><…

JAVA-08-继承

继承 父类&#xff1a;被继承的类 子类&#xff1a;继承父类的类&#xff0c;可以访问父类的公有和保护成员。 extends:使用 extends 关键字来表示一个类继承另一个类。 方法重写:子类可以重写父类的方法&#xff0c;以提供特定的实现。重写的方法必须与父类中的方法具有相…

Trimble X12三维激光扫描仪正在改变游戏规则【上海沪敖3D】

Trimble X12 三维激光扫描仪凭借清晰、纯净的点云数据和亚毫米级的精度正在改变游戏规则。今天的案例我们将与您分享&#xff0c;X12是如何帮助专业测量咨询公司OR3D完成的一个模拟受损平转桥运动的项目。 由于习惯于以微米为单位工作&#xff0c;专业测量机构OR3D是一家要求…

SpringBoot框架下的资产管理创新

4系统概要设计 4.1概述 系统设计原则 以技术先进、系统实用、结构合理、产品主流、低成本、低维护量作为基本建设原则&#xff0c;规划系统的整体构架. 先进性&#xff1a; 在产品设计上&#xff0c;整个系统软硬件设备的设计符合高新技术的潮流&#xff0c;媒体数字化、压缩、…

统信UOS开发环境支持Perl

UOS凭借广泛的编程语言支持,为开发者构建了一个高效灵活的开发环境,无需担心环境兼容性问题。 文章目录 一、环境部署1. Perl开发环境安装2. Perl开发环境配置环境变量配置模块管理器编辑器集成调试工具二、代码示例文件处理Web开发三、常见问题1. 依赖管理问题2. 性能问题3.…

qt QClipboard详解

1、概述 QClipboard是Qt框架中的一个类&#xff0c;它提供了对窗口系统剪贴板的访问能力。剪贴板是一个临时存储区域&#xff0c;通常用于在应用程序之间传递文本、图像和其他数据。QClipboard通过统一的接口来操作剪贴板内容&#xff0c;使得开发者能够方便地实现剪切、复制和…