golang 泛型详解

目录

概念

~int vs .int

常见的用途和错误

结论:


概念

Go 在1.18 中添加了泛型,这样Go 就可以在定义时不定义类型,而是在使用时进行类型的定义,并且还可以在编译期间对参数类型进行校验。Go 目前只支持泛型方法,还不支持泛型接口,下面我会详细介绍Go的泛型使用以及常见的使用错误。其他概念性的东西我不想说太多,大家可以自己百度。

我们先看一个例子,很简单就是从map[string]int 提出所有的键的函数:

func getKeys(m map[string]int) []string {var keys []stringfor k := range m {keys = append(keys, k)}return keys
}

如果我们现在需要的是map[int]int,那么我们该怎么做呢?在使用泛型之前,Go 开发人员有几种选择:使用代码生成、反射或重复代码。例如,我们可以编写两个函数,每个函数对应一种 map 类型,或者甚至尝试扩展 getKeys 以接受不同的 map 类型:

func getKeys(m any) ([]any, error) {                      ❶switch t := m.(type) {default:return nil, fmt.Errorf("unknown type: %T", t)     ❷case map[string]int:var keys []anyfor k := range t {keys = append(keys, k)}return keys, nilcase map[int]string:// Copy the extraction logic}
}

❶ 接受和返回一个interface{}

❷如果不是我们需要的类型,返回一个错误

通过这个例子,我们注意到了一些问题。首先,它增加了模版代码。事实上我们想增加一个case 时,需要重复range 循环。同时函数现在接受任意类型,这意味着我们失去了GO作为类型化语言的一些优势。事实上,检查类型是否支持是在运行时而不是编译时进行的。因此如果类型不合法,我们还需要返回错误。最后,由于key 的类型是int或者字符串,我们必须返回interface{} 的切片。如果对切片进行处理,还需要断言操作。这种方法增加了调用方的工作量。现在有了泛型,我们可以重构这段代码。

下面是泛型的基本语法

类型参数是我们可以与函数和类型一起使用的通用类型。例如,以下函数接受一个类型参数:

func foo[T any](t T) {     ❶// ...
}

❶ T 是类型参数

在调用 foo 时,我们传递一个任意类型的类型参数。提供类型参数称为实例化,这项工作在编译时完成。这样,类型安全就成了核心语言特性的一部分,而 避免了运行时的开销。

让我们回到 getKeys 函数,使用类型参数编写一个通用版本,它可以接受任何类型的映射:

func getKeys[K comparable, V any](m map[K]V) []K {   ❶var keys []K                                     ❷for k := range m {keys = append(keys, k)}return keys
}

❶ K 是可以比较的,value 是 interface{}

❷ 创建一个K slice

我们这么定义的原因是map 的key 必须是可以比较的类型,不能是 any。比如我们不能用slices:

var m map[[]byte]int

这段代码会导致编译错误:映射键类型 []byte 无效。因此,我们不能接受任何键类型,而必须限制类型参数,使键类型符合特定要求。这里的要求是键类型必须是可比较的(我们可以使用 == 或 !=)。因此,我们将 K 定义为可比类型,而不是任意类型。

限制类型参数以满足特定要求称为约束。约束是一种接口类型,可以包含:一组行为(方法),任意类型 让我们举一个具体的例子来说明后者。假设我们不想接受任何可比较的映射键类型。例如,我们想将其限制为 int 或字符串类型。我们可以这样定义一个自定义约束:

type customConstraint interface {~int | ~string                   ❶
}
func getKeys[K customConstraint,     ❷V any](m map[K]V) []K {}

❶ 定义一个自定义类型,将类型限制为 int 和字符串 ❷ 将类型参数 K 改为自定义约束类型

首先,我们定义了一个 customConstraint 接口,使用联合运算符 | 将类型限制为 int 或字符串(稍后我们将讨论 ~ 的使用)。现在,K 是一个 customConstraint,而不是之前的可比类型。

getKeys 的签名强制要求我们可以使用任何值类型的 map 调用它,但键类型必须是 int 或字符串--例如,在调用方:

m = map[string]int{"one":   1,"two":   2,"three": 3,
}
keys := getKeys(m)

请注意,Go 可以推断出 getKeys 是以字符串类型参数调用的。之前的调用等同于此:

keys := getKeys[string](m)

~int vs .int

int 就是变量声明int 类型比如 var a int 而 ~int 指的是底层为int 类型,如 type b int , b 和 int 是两个不同的类型,但是b 的底层是int 类型

type customConstraint interface {~intString() string
}
type customInt int
​
func (i customInt) String() string {return strconv.Itoa(int(i))
}
func getKeys[keys customConstraint, v any](m map[keys]v) {}
func main() {t := customInt(1)getKeys(map[customInt]any{t: "a"})
}

由于 customInt 是一个 int 并实现了 String() 字符串方法,因此 customInt 类型满足已定义的约束。但是,如果我们将约束条件改为包含 int 而不是 ~int,那么使用 customInt 就会导致编译错误,因为 int 类型没有实现 String() 字符串。

到目前为止,我们已经讨论了在函数中使用泛型的示例。不过,我们也可以在数据结构中使用泛型。例如,我们可以创建一个包含任意类型值的链表。为此,我们将编写一个 Add 方法来追加一个节点:

type Node[T any] struct {                ❶Val  Tnext *Node[T]
}func (n *Node[T]) Add(next *Node[T]) {   ❷n.next = next
}

❶ 用一个类型参数

❷ 实例化一个类型接受者

在示例中,我们使用类型参数来定义 T,并在 Node 中使用这两个字段。关于方法,接收器是实例化的。事实上,由于 Node 是泛型的,它也必须遵循定义的类型参数。

关于类型参数,最后需要注意的一点是,它们不能与方法参数一起使用,只能与函数参数或方法接收器一起使用。例如,下面的方法将无法编译:

如果我们想在方法中使用泛型,那么接收器就必须是一个类型参数。现在,让我们来看看应该和不应该使用泛型的具体情况。

常见的用途和错误

讲到这里大家可能会问泛型如何使用呢,现在我们就讨论泛型几种常见的用途

  1. 数据结构--例如,如果我们实现的是二叉树、链表或堆,我们可以使用泛型来确定元素类型。

  2. 处理任何类型的切片、映射和通道的函数--例如,合并两个通道的函数可以处理任何类型的通道。因此,我们可以使用类型参数来确定通道类型:

比如在通道中我们对channel 类型进行限制了:

type chantype interface {int | string
}
​
func merge1[T chantype](ch1, ch2 <-chan T) <-chan T {// ...
}
  1. 例如,排序软件包包含一个 sort.Interface 接口,其中有三个方法:

    type Interface interface {Len() intLess(i, j int) boolSwap(i, j int)
    }

该接口被不同的函数使用,如 sort.Ints 或 sort .Float64。我们该怎么做呢?很多时候我们可能会想到用interface{} 抽象然后断言,用了泛型我们就可以提供一个模版,实例化什么参数由用户去决定了。

type SliceFn[T any] struct {S       []T  Compare func(T, T) bool
}
​
func (s SliceFn[T]) Len() int {return len(s.S)
}
​
func (s SliceFn[T]) Less(i, j int) bool {return s.Compare(s.S[i], s.S[j])
}
​
func (s SliceFn[T]) Swap(i, j int) {s.S[i], s.S[j] = s.S[j], s.S[i]
}
​
func main() {s := SliceFn[int]{S: []int{1, 2, 3},Compare: func(a int, b int) bool {return a > b},}sort.Sort(s)fmt.Println(s.S)
}

在本例中,通过分解行为,我们可以避免为每种类型创建一个函数。可以发现其实泛型比interface 更为抽象

结论:

先看一个例子:

func foo[T io.Writer](w T) {b := getBytes()_, _ = w.Write(b)
}

在调用参数类型的方法时--例如,考虑一个接收 io.Writer 并调用 Write 方法的函数:

在这种情况下,使用泛型不会给我们的代码带来任何价值。我们应该直接将 w 参数设为 io.Writer。 当泛型使我们的代码更复杂时--泛型从来不是强制性的,作为 Go 开发者,我们已经在没有泛型的情况下生活了十多年。如果我们在编写泛型函数或结构时发现它并没有让我们的代码变得更清晰,那么我们或许应该重新考虑我们在该特定用例中的决定。 虽然泛型在特定情况下会有所帮助,但我们应该谨慎对待何时使用、何时不使用泛型。一般来说,如果我们想回答何时不使用泛型,我们可以找到与何时不使用接口的相似之处。事实上,泛型引入了一种抽象形式,而我们必须记住,不必要的抽象会带来复杂性。 我们还是那句话,不要用不必要的抽象来污染我们的代码,现在还是专注于解决具体问题吧。这意味着我们不应过早使用类型参数。等到我们要编写模板代码时,再考虑使用泛型吧。

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

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

相关文章

Machine Vision Technology:Lecture2 Linear filtering

Machine Vision Technology&#xff1a;Lecture2 Linear filtering Types of ImagesImage denoising图像去噪Defining convolution卷积的定义Key properties卷积的关键属性卷积的其它属性Annoying details卷积练习Sharpening锐化Gaussian KernelNoise噪声 分类Gaussian noise高…

HTTP详解(HTTP的特点,状态码,工作原理,GET和POST的区别,如何解决无状态通信)!!!

文章目录 一、HTTP协议简介二、HTTP的主要特点三、HTTP之URL四、Request和Respons五、HTTP的状态码六、HTTP工作原理七、GET和POST请求的区别八、解决HTTP无状态通信——Cookie和Session 一、HTTP协议简介 HTTP协议是Hyper Text Transfer Protocol&#xff08;超文本传输协议&…

iOS App冷启动优化:Before Main阶段

iOS应用冷启动时&#xff0c;在 UIApplicationMain(argc, argv, nil, appDelegateClassName)方法执行前&#xff0c;主要经历以下阶段&#xff1a; 1. 执行exec&#xff08;&#xff09;启动应用程序进程 2. 加载可执行文件&#xff0c;即将应用程序的Mach-O文件加载到内存…

31-树-找树左下角的值

这是树的第31篇算法&#xff0c;力扣链接。 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 示例 1: 输入: root [2,1,3] 输出: 1 拿到这道题的第一想法&#xff0c;我可以层级遍历选取最左节点。 func f…

R语言数据可视化之美专业图表绘制指南(增强版):第1章 R语言编程与绘图基础

第1章 R语言编程与绘图基础 目录 第1章 R语言编程与绘图基础前言1.1 学术图表的基本概念1.1.1 学术图表的基本作用1.1.2基本类别1.1.3 学术图表的绘制原则 1.2 你为什么要选择R1.3 安装 前言 这是我第一次在博客里展示学习中国作者的教材的笔记。我选择这本书的依据是作者同时…

C#学习:初识各类应用程序

编写我们第一个程序——Hello,World! 1.编程不是“学”出来的&#xff0c;而是“练”出来的 2.在反复应用中积累&#xff0c;忽然有一天就会顿悟 3.学习原则&#xff1a; 3.1从感官到原理 3.2从使用别人的到创建自己的 3.3必需亲自动手 3.4必需学以致用&#xff0c;紧跟实际…

计算机网络原理--传输层

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f4d5;格言&#xff1a;那些在暗处执拗生长的花&#xff0c;终有一日会馥郁传香欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 TCP/IP五层&#xff08;或四层&#xff09;模型 传输层 TCP和UDP的区别 UDP协议 校验和 如何…

Linux 基础之 vmstat 命令详解

文章目录 一、前言二、使用说明2.1 vmstat [delay/count/d/D/t/w]2.2.vm模式的字段 一、前言 vmstat(VirtualMeomoryStatistics&#xff0c;虚拟内存统计)是一个不错的 Linux/Unix 监控工具&#xff0c;在性能测试中除了top外也是比较常用的工具之一&#xff0c;它可以监控操作…

同局域网共享虚拟机(VMware)

一、前言 首先我们先来了解下 VMware 的三种网络模式桥接模式、NAT模式、仅主机模式&#xff0c;网络类型介绍详情可以参考下我之前的文档 Linux系统虚拟机安装&#xff08;上&#xff09;第三章 - 第9步指定网络类型。了解三种网络模式的原理之后&#xff0c;再来剖析下需求&…

Python爬虫——Urllib库-上

这几天都在为了蓝桥杯做准备&#xff0c;一直在刷算法题&#xff0c;确实刷算法题的过程是及其的枯燥且枯燥的。于是我还是决定给自己找点成就感出来&#xff0c;那么Python的爬虫就这样开始学习了。 注&#xff1a;文章源于观看尚硅谷爬虫视频后笔记 目录 Urllib库 基本使…

自定义View中的ListView和ScrollView嵌套的问题

当我们在使用到ScrollView和ListView的时候可能会出现显示不全的问题。那我们可以进行以下分析 ScrollView在测量子布局的时候会用UNSPECIFIED。通过源码观察&#xff0c; 在ScrollView的onMeasure方法中 Overrideprotected void onMeasure(int widthMeasureSpec, int heightMe…

MySQL进阶:大厂高频面试——各类SQL语句性能调优经验

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;MySQL进阶&#xff1a;强推&#xff0c;冲大厂必精通&#xff01;MySQL索引底层&#xff08;BTree&#xff09;、性能分析、使用…

HTTP笔记(五)

个人学习笔记&#xff08;整理不易&#xff0c;有帮助点个赞&#xff09; 笔记目录&#xff1a;学习笔记目录_pytest和unittest、airtest_weixin_42717928的博客-CSDN博客 目录 一&#xff1a;HTTP报文首部 &#xff08;1&#xff09;HTTP请求报文 &#xff08;2&#xff09…

Kaggle 竞赛入门

打比赛不用写算法源码&#xff0c;应用的时候不用自己写。学习的时候可以自己写。 Kaggle 竞赛入门 认识 Kaggle 平台Kaggle竞赛知识前提结构化数据前提图像数据文本数据 Kaggle竞赛套路一个赛题的完整流程 认识 Kaggle 平台 Kaggle 官网 主页&#xff0c;比赛&#xff08;数据…

复现nerfstudio并训练自己制作的数据集

网站&#xff1a;安装 - nerfstudio GitHub - nerfstudio-project/nerfstudio&#xff1a;NeRF 的协作友好工作室 安装之前要确保电脑上已经有CUDA11.8或以上版本&#xff08;更高版本的可以安装11.8的toolkit&#xff09; 创建环境 conda create --name nerfstudio -y pyt…

浅谈 Linux 网络编程 - Server 端模型、sockaddr、sockaddr_in 结构体

文章目录 前言前置知识Server 端核心模型 【重点】相关函数 【重点】socket 函数bind 函数listen 函数accept 函数close 函数 sockaddr 数据结构 【重点】 前言 本文主要是对 Linux 网络编程中&#xff0c;Server 端的模型、相关函数 以及 sockaddr、sockaddr_in 结构体做介绍…

黑马程序员——接口测试——day05——Request库、Cookie、Session、UnitTest框架

目录&#xff1a; Requests库 Requests库安装和简介设置http请求语法应用案例 案例1案例2案例3案例4Cookie Cookie简介CookieSession认证方式案例5-看演示&#xff0c;此代码不需实现Session Session简介Session自动管理Cookie案例6面试题Cookie和Session区别获取指定响应数据…

300分钟吃透分布式缓存(拉钩教育总结)

开篇寄语 开篇寄语&#xff1a;缓存&#xff0c;你真的用对了吗&#xff1f; 你好&#xff0c;我是你的缓存老师陈波&#xff0c;可能大家对我的网名 fishermen 会更熟悉。 我是资深老码农一枚&#xff0c;经历了新浪微博从起步到当前月活数亿用户的大型互联网系统的技术演进…

Linux|centos7|yum和编译安装ImageMagick记录

一&#xff0c; yum安装imagemagick imagemagick这个软件是图像文件的处理神器&#xff0c;可以文字转图像以及图像的剪辑等等工作&#xff0c;也是配合人工智能工程的不可或缺的工具&#xff0c;具体的用处和特点就不在这里废话了&#xff0c;有兴趣的百度就行了 本次是在…

SpringBoot底层原理

SpringBoot底层原理 一 配置优先级 1.配置方式 Springboot中支持三种配置方式&#xff0c;分别为&#xff1a; application.propertiesapplication.ymlapplication.yaml 2.配置优先级 当存在多份配置文件时&#xff0c;配置文件会按照它们的优先级生效。 优先级从高到底…