Go语言进阶篇——泛型

前言

在开始今天有关泛型的介绍之前,我们先来看一个简单的例子,如果我们要设计两个int类型变量相加的函数,我们可以这样设计:

func Sum (a, b int) int {return a + b
}

如果变量要求是float类型或者是其他类型,我们要面对一个问题:我们真的要为每个类型去编写对应的函数吗当然这个是非常影响开发效率的,而我们要如何解决这个问题呢?这就是我们几天要提到的泛型了。

什么是泛型

泛型,顾名思义,它是为了执行逻辑与类型无关的问题,这类问题不关心给出的类型是什么,只需要完成对应的操作就足够,我们可以尝试把上面的问题改成泛型的写法:

func Sum [T int|float64](a, b T) T{return a + b
}

类型形参:T就是一个类型形参,形参具体是什么类型取决于传进来什么类型

类型约束int | float64构成了一个类型约束,这个类型约束内规定了哪些类型是允许的,约束了类型形参的类型范围

类型实参Sum[int](1,2),手动指定了int类型,int就是类型实参。

第一种用法,显式的指明使用哪种类型,如下

Sum[int](2012, 2022)

第二种用法,不指定类型,让编译器自行推断,如下

Sum(3.1415926, 1.114514)

通过上面的介绍,相信大家对泛型有所了解了,在日常使用泛型时,我们要注意将泛型引入项目后,开发上确实会比较方便,随之而来的是项目复杂度的增加,毫无节制的使用泛型会使得代码难以维护,所以应该在正确的地方使用泛型,而不是为了泛型而泛型。

泛型结构

泛型切片

首先我们定义一个泛型切片,它的类型约束时int|float32|float64

type GenericsSlice[T int|float32|float64] []T

我们如何去使用这种泛型切片呢?让我们来看一下下面这个案例:

package mainimport "fmt"func main() {type GenericsSlience[T int | float64] []T//创建整形切片var ints GenericsSlience[int]ints = append(ints, 1)//创建浮点型切片var floats GenericsSlience[float64]floats = append(floats, 1.1)//打印切片fmt.Println(ints)fmt.Println(floats)
}

输出为:

在这里插入图片描述

泛型哈希表

我们定义泛型哈希表的时候,要保证键的类型必须是可以比较的,所以我们会使用comparable接口,下面我们可以尝试定义一个哈希表:

type GenericsMap[K comparable,V int|string|byte] map[K]V

使用时:

gmap1 := GenericMap[int, string]{1: "hello world"}
gmap2 := make(GenericMap[string, byte], 0)

泛型结构体

这是一个泛型结构体,类型约束为T int | string

解释type GenericStruct[T int | string] struct {Name stringId   T
}

使用:

解释GenericStruct[int]{Name: "jack",Id:   1024,
}
GenericStruct[string]{Name: "Mike",Id:   "1024",

注意:在结构体中如果我们要使用切片,一般推荐下面这种写法:

type Company[T int | string, S int | string] struct {Name  stringId    TStuff []S
}

泛型接口

我们来看一个泛型接口的简单应用:

package mainimport "fmt"type Sayable[T int | float64 | string] interface {Say() T
}type Person[T int | float64 | string] struct {msg T
}func (p Person[T]) Say() T {return p.msg
}func main() {var s Sayable[string]s = Person[string]{"hello world"}fmt.Println(s.Say())
}

泛型结构的使用注意点

  1. 泛型不能作为一个类型的基本类型
  2. 泛型类型无法使用类型断言
  3. 匿名结构体/函数不支持泛型
  4. 不支持泛型方法,这里要说明一下,主要是不支持泛型形参,比如下面这样:
package mainimport "fmt"type Sayable[T int | float64 | string] interface {Say() T
}type Person[T int | float64 | string] struct {msg T
}func (p Person[T]) Say() T {return p.msg
}func main() {var s Sayable[string]s = Person[string]{"hello world"}fmt.Println(s.Say())
}

这样是无法通过编译的。

泛型使用示例

模拟队列

package maintype Queue[T any] []Tfunc (q *Queue[T]) Push(e T) {*q = append(*q, e)
}func (q *Queue[T]) Pop() (_ T) {if q.Size() > 0 {res := q.Peek()*q = (*q)[1:]return res}return
}func (q *Queue[T]) Peek() (_ T) {if q.Size() > 0 {res := (*q)[0]return res}return
}func (q *Queue[T]) Size() int {return len(*q)
}

PopPeek方法中,可以看到返回值是_ T,这是具名返回值的使用方式,但是又采用了下划线_表示这是匿名的,这并非多此一举,而是为了表示泛型零值。由于采用了泛型,当队列为空时,需要返回零值,但由于类型未知,不可能返回具体的类型,借由上面的那种方式就可以返回泛型零值。也可以声明泛型变量的方式来解决零值问题,对于一个泛型变量,其默认的值就是该类型的零值

上面队列的例子,由于对元素没有任何的要求,所以类型约束为any。但堆就不一样了,堆是一种特殊的数据结构,它可以在O(1)的时间内判断最大或最小值,所以它对元素有一个要求,那就是必须是可以排序的类型,但是go语言内置可以比较的类型就只有字符串和数字,同时泛型约束不允许带方法的接口,所以在对初始化的时候就需要我们传入一个自定义的比较器

下面我们来尝试来实现一个简单的最小二根堆:

package maintype Comparator[T any] func(a, b T) inttype BinaryHeap[T any] struct {data       []Tcomparator Comparator[T]
}func (Heap *BinaryHeap[T]) Peek() (_ T) {if Heap.Size() > 0 {return Heap.data[0]}return
}func (Heap *BinaryHeap[T]) Pop() (_ T) {if Heap.Size() > 0 {res := Heap.Peek()Heap.data = Heap.data[1:]return res}return
}func (Heap *BinaryHeap[T]) Push(value T) {Heap.data = append(Heap.data, value)Heap.up(Heap.Size() - 1)
}func (Heap *BinaryHeap[T]) Size() int {return len(Heap.data)
}func (Heap *BinaryHeap[T]) up(i int) {if Heap.Size() == 0 || i < 0 || i >= Heap.Size() {return}for parentindex = i>>1 - 1; parentindex >= 0; parentindex = i>>1 - 1 {if Heap.comparator(Heap.data[i], Heap.data[parentindex]) < 0 {Heap.data[i], Heap.data[parentindex] = Heap.data[parentindex], Heap.data[i]i = parentindex} else {break}}
}func (Heap *BinaryHeap[T]) down() {if heap.Size() == 0 || i < 0 || i >= heap.Size() {return}size := heap.Size()for lsonIndex := i<<1 + 1; lsonIndex < size; lsonIndex = i<<1 + 1 {rsonIndex := lsonIndex + 1if rsonIndex < size && heap.compare(heap.s[rsonIndex], heap.s[lsonIndex]) < 0 {lsonIndex = rsonIndex}// less than or equal toif heap.compare(heap.s[i], heap.s[lsonIndex]) <= 0 {break}heap.s[i], heap.s[lsonIndex] = heap.s[lsonIndex], heap.s[i]i = lsonIndex}
}

使用起来如下

type Person struct {Age  intName string
}func main() {heap := NewHeap[Person](10, func(a, b Person) int {return cmp.Compare(a.Age, b.Age)})heap.Push(Person{Age: 10, Name: "John"})heap.Push(Person{Age: 18, Name: "mike"})heap.Push(Person{Age: 9, Name: "lili"})heap.Push(Person{Age: 32, Name: "miki"})fmt.Println(heap.Peek())fmt.Println(heap.Pop())fmt.Println(heap.Peek())
}

输出

{9 lili}
{9 lili} 
{10 John}

有泛型的加持,原本不可排序的类型传入比较器后也可以使用堆了,这样做肯定比以前使用interface{}来进行类型转换和断言要优雅和方便很多。

总结

go的一大特点就是编译速度非常快,编译快是因为编译期做的优化少,泛型的加入会导致编译器的工作量增加,工作更加复杂,这必然会导致编译速度变慢,事实上当初go1.18刚推出泛型的时候确实导致编译更慢了,go团队既想加入泛型又不想太拖累编译速度,开发者用的顺手,编译器就难受,反过来编译器轻松了(最轻松的当然是直接不要泛型),开发者就难受了,现如今的泛型就是这两者之间妥协后的产物。

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

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

相关文章

使用Java语言,解决斐波那契兔子问题

/* 一对兔子&#xff0c;从出生后第三个月起每个月都生一对兔子&#xff0c;小兔子长到第三个月后每个月又生一对兔子&#xff0c; 假如兔子都不死&#xff0c;问第二十个月的兔子对数为多少&#xff1f; 1 1 2 3 5 8 13 1 1 1 1 1 1 11 1 1 1 11 1 1 11 1 11 1 11 11 11 11111…

高防服务器是怎样进行防御的?

随着互联网的发展&#xff0c;网络攻击和恶意流量也日益增多&#xff0c;高防服务器作为企业网络安全的重要保障&#xff0c;越来越受到关注。那么&#xff0c;高防服务器是怎样进行防御的呢&#xff1f; 高防服务器主要是指具备防御DDoS攻击、CC攻击、7x24小时实时防御网站入…

用c++实现背包问题动态规划求解算法

背包问题是一类组合优化的问题。在其最简单的形式中,问题模型是这样的:假设你有一个能承受最大重量为W的背包和一系列物品,每个物品都有其自身的重量和价值,目标是确定哪些物品应该被选中放入背包,使得背包内物品的总价值最大,同时不超过背包的最大承重。 下面是使用C++…

【Flink集群RPC通讯机制(二)】创建AkkaRpcService、启动RPC服务、实现相互通信

文章目录 零. RpcService服务概述1. AkkaRpcService的创建和初始化2.通过AkkaRpcService初始化RpcServer3. ResourceManager中RPC服务的启动4. 实现相互通讯能力 零. RpcService服务概述 RpcService负责创建和启动Flink集群环境中RpcEndpoint组件的RpcServer&#xff0c;且Rpc…

多模态相关论文笔记

(cilp) Learning Transferable Visual Models From Natural Language Supervision 从自然语言监督中学习可迁移的视觉模型 openAI 2021年2月 48页 PDF CODE CLIP(Contrastive Language-Image Pre-Training)对比语言图像预训练模型 引言 它比ImageNet模型效果更好&#xff0c…

32单片机基础:OLED调试工具的使用

下面会介绍OLED显示屏的驱动函数模块&#xff0c;先学会如何使用&#xff0c;至于OLED屏幕的原理和代码编写&#xff0c; 我们之后会再写一篇。 现在我们就是用OLED当一个调试的显示屏&#xff0c;方便我们调试程序。 为什么要调试呢&#xff0c;是为了方便我们看现象&#…

vue中组件和插件有什么区别?

vue中组件和插件有什么区别&#xff1f; 回答思路&#xff1a;组件是什么&#xff1f;-->插件是什么&#xff1f;-->组件和插件的区别&#xff1f;-->他们的使用场景&#xff1f;组件是什么&#xff1f;组件的优势&#xff1a; 插件是什么&#xff1f;组件和插件的区别…

unity——shader基础知识点 学习笔记【个人复习向/侵删/有不足之处欢迎斧正】

一、shaderLab 1.pass渲染通道 具体实现着色器代码的地方&#xff0c;每个subshader至少有一个pass。 可以利用UsePass命令在其他Shader当中复用该Pass的代码&#xff0c;只需要在其他Shader当中使用 UsePass ”Shader路径/Pass名”&#xff0c;Unity内部会把Pass名称转换为大…

嵌入式学习之Linux入门篇——使用VMware创建Unbuntu虚拟机

目录 主机硬件要求 VMware 安装 安装Unbuntu 18.04.6 LTS 新建虚拟机 进入Unbuntu安装环节 主机硬件要求 内存最少16G 硬盘最好分出一个单独的盘&#xff0c;而且最少预留200G&#xff0c;可以使用移动固态操作系统win7/10/11 VMware 安装 版本&#xff1a;VMware Works…

Java Clip 播放MP3音频

Java Clip 播放MP3音频 1 依赖2 接口3 实现4 测试 项目Value音频格式 添加依赖*.wav(JDK 原生支持)*.pcm(JDK 原生支持)*.au(JDK 原生支持)*.aiff(JDK 原生支持)*.mp3mp3spi.jar*.flacjflac-codec.jar 1 依赖 <dependency><groupId>com.googlecode.soundlibs<…

Python | OS模块操作

一、介绍 Python的os模块提供了许多与操作系统交互的函数&#xff0c;可以用于文件和目录的操作、进程管理、环境变量的访问等。以下是os模块的一些常用功能&#xff1a; 文件和目录操作&#xff1a;os模块提供了许多函数来进行文件和目录的操作&#xff0c;如创建文件夹(os.mk…

小红书如何写标题吸引人?

好奇类&#xff1a;利用观众得不到&#xff0c;没体验过的事物&#xff0c;激发好奇心 万能公式:&#xff08; )是一种什么体验&#xff1f; 例如:日入过千是一种什么体验? 极限类&#xff1a;将抽象的内容具象化&#xff0c;吸引力强&#xff0c;让观众引起重视 万能公式…

CQT新里程碑:SOC 2 数据安全认证通过,加强其人工智能支持

Covalent Network&#xff08;CQT&#xff09;发展新里程碑&#xff1a;SOC 2 数据安全认证通过&#xff0c;进一步加强了其人工智能支持 Covalent Network&#xff08;CQT&#xff09;现已完成并通过了严格的 Service Organization Control&#xff08;SOC) 2 Type II 的合规性…

物联网和人工智能的融合

物联网和人工智能的融合 1. 物联网和人工智能的融合2. 芯片技术的进步3. 安全和隐私保护挑战4. 软件开发和调试技术的创新5. 自动化和智能化趋势 1. 物联网和人工智能的融合 随着物联网和人工智能技术的快速发展&#xff0c;嵌入式系统将更多地与物联网设备和人工智能算法相结…

vivo 基于 StarRocks 构建实时大数据分析平台,为业务搭建数据桥梁

在大数据时代&#xff0c;数据分析和处理能力对于企业的决策和发展至关重要。 vivo 作为一家全球移动互联网智能终端公司&#xff0c;需要基于移动终端的制造、物流、销售等各个方面的数据进行分析以满足业务决策。 而随着公司数字化服务的演进&#xff0c;业务诉求和技术架构有…

ELK Stack 日志平台搭建

前言 最近在折腾 ELK 日志平台&#xff0c;它是 Elastic 公司推出的一整套日志收集、分析和展示的解决方案。 专门实操了一波&#xff0c;这玩意看起来简单&#xff0c;但是里面的流程步骤还是很多的&#xff0c;而且遇到了很多坑。在此记录和总结下。 本文亮点&#xff1a;…

windows系统中jenkins构建报错提示“拒绝访问”

一.背景 之前徒弟在windows中安装的jenkins,运行的时候用的是java -jar jenkins.war来运行的。服务器只有1个盘符C盘。今天说构建错误了&#xff0c;问我修改了啥&#xff0c;我年前是修改过构建思路的。 二.问题分析 先看jenkins构建任务的日志&#xff0c;大概是xcopy命令执…

【linux】使用g++调试内存泄露:AddressSanitizer

1、简介 AddressSanitizer(又名 ASan)是 C/C++ 的内存错误检测器。它可以用来检测: 释放后使用(悬空指针) 堆缓冲区溢出 堆栈缓冲区溢出 全局缓冲区溢出 在作用域之后使用 初始化顺序错误 内存泄漏这个工具非常快,只将被检测的程序速度减慢约2倍,而Valgrind将会是程序…

JavaScript:问号?的多种用法

文章目录 条件运算符 (三元运算符)可选链操作符 (?.)空值合并操作符 (??)逻辑赋值运算符&#xff08;?? &#xff09;补充&#xff1a;&#xff08;&&、||&#xff09; 正则表达式中 条件运算符 (三元运算符) 早在ES1&#xff08;ECMAScript 1st Edition&#xf…

word软件中硬件图像加速有什么用处?禁用硬件图形加速(G)会影响word文档中插入图片的分辨率吗?

问题描述&#xff1a;word软件中硬件图像加速有什么用处&#xff1f;禁用硬件图形加速(G)会影响word文档中插入图片的分辨率吗&#xff1f; 问题解答&#xff1a; 在 Microsoft Word 中&#xff0c;硬件图形加速主要用于提高图形元素的渲染速度和性能&#xff0c;特别是处理大…