Go进阶概览 -【7.2 泛型的使用与实现分析】
7.2 泛型的使用与实现分析
泛型是Go 1.18引入的概念,在引入这个概念前经过了好几年的考量最终才将这这个特性加进去。
泛型在多种语言中都是存在的,比如C++、Java等语言中都有泛型的概念。
本节我们将针对泛型的使用、实现原理进行整体的讲解。
本节代码存放目录为 lesson20
泛型基础
什么是泛型?
简单来说,泛型与空接口interface{}相似但又有不同。我们知道空接口可以用来标识任意的类型,其实泛型也是干这件事情的。
那么既然有了接口,为什么还要出现泛型呢?这需要结合我们之前章节的反射来一起看待。
在我们使用interface{}与反射来进行处理时,其实都是在运行时处理,运行时处理那么不可避免的就会出现性能开销与安全性问题。
而泛型则是在编译阶段进行处理的,而不是运行时处理,所以不管是从性能还是安全性来说,泛型都是一种更好的选择。
另外泛型主要用于函数与类型的的定义,而不能用于普通变量的定义,这也是与interface{}的主要区别。
泛型的主要应用是在函数的定义上。当我们有一些公用函数,比如说:打印、求和等,在没有泛型的时候,我们需要定义一个参数为int型的Print、一个参数为string型的Print,但是如果是泛型的话我们中需要定义一个即可。
泛型函数的实现与应用
- 简单泛型函数
func Print[T any](input T) {fmt.Println(input)
}Print(1)
Print("hello")
在上面的代码中,我们通过泛型仅定义了一个函数,即实现了传递int、string参数的目的。
定义的格式也是固定的:func funcName[T any](arg T),其中[T any]就标识这个函数是一个泛型函数。
- 多个类型参数
func Add[T int | float64](a, b T) T {return a + b
}fmt.Println(Add(1, 2))
fmt.Println(Add(1.5, 2.3))
在上面的代码中,我们传入了多个参数,同时我们可以看到,使用了[T int | float64]这样的格式。
那么这种格式是什么意思呢?如果我们这样写,其实就代表这个函数接收的参数只能是int、float64两种类型的。
基于我们函数的功能,如果传入string、结构体等类型的参数,那么肯定是不符合的,所以我们可以在函数中就指定好传入的类型范围。
泛型结构体
在结构体中使用泛型也是比较常用的一个操作,比如我们的结构体字段是相同的,但是会接收不同类型的值,那么使用泛型也是一个很好的选择。
type Container[T any] struct {value T
}intContainer := Container[int]{value: 42}
fmt.Println(intContainer.value)stringContainer := Container[string]{value: "hello"}
fmt.Println(stringContainer.value)
结构体泛型使用会比较广泛,特别是在一些算法或数据结构类型的场景。比如说实现一个栈、一个队列,那么我们就可以使用泛型来实现,这样栈就可以存储多种数据类型。
type Stack[T any] struct {items []T
}func (s *Stack[T]) Push(item T) {s.items = append(s.items, item)
}func (s *Stack[T]) Pop() T {n := len(s.items)item := s.items[n-1]s.items = s.items[:n-1]return item
}// 创建一个整数栈
intStack := Stack[int]{}
intStack.Push(1)
intStack.Push(2)
fmt.Println(intStack.Pop())// 创建一个字符串栈
stringStack := Stack[string]{}
stringStack.Push("hello")
stringStack.Push("world")
fmt.Println(stringStack.Pop())
通过泛型我们就可以简单的进行处理,这种方法其实是比interface{}高效很多的。
泛型的约束与接口
-
基本泛型约束
// Compare 约束 T 必须可比较(类型必须实现了comparable接口) func Compare[T comparable](a, b T) bool {return a == b }fmt.Println(Compare(1, 2)) fmt.Println(Compare("Go", "Go"))在上面的代码中,
T类型参数使用了comparable作为约束,表示T必须是支持比较操作的类型,例如整数、字符串等。comparable是Go的内置接口,用于表示可以比较的类型(支持==和!=操作)。 -
自定义接口作为约束
// Stringer 定义一个接口 type Stringer interface {String() string }// PrintString 泛型函数,T 必须实现 Stringer 接口 func PrintString[T Stringer](item T) {fmt.Println(item.String()) }// Person 实现 Stringer 接口的类型 type Person struct {Name string }func (p Person) String() string {return p.Name }在这个例子中,泛型函数
PrintString限定类型参数T必须实现Stringer接口。也就是说
PrintString只能用于那些实现了Stringer接口的类型,比如Person。 -
内置的泛型约束
// Number 泛型约束 T 必须是 int 类型的别名 type Number interface {~int }func Sum[T Number](a, b T) T {return a + b }type MyInt int // MyInt 是 int 的别名var a MyInt = 10 var b MyInt = 20 fmt.Println(Sum(a, b))在上面的代码中,
~int表示类型参数T可以是int或任何int的别名类型(如MyInt)。
实现原理
如果了解Java的话我们可以知道,Java只要函数参数的类型不同,那么函数名称可以是相同的。
在Go语言中,泛型其实差不多就是这么实现的。在编译的时候,编译器会生成多个类型的函数。
如下代码所示:
func Print[T any](input T) {fmt.Println(input)
}Print(1)
Print("hello")
在上面的代码中,我们实现了一个简单的打印函数,调用的时候传入了int与string类型的数据。
那么在我们编译的时候,编译器可能会生成下面的代码:
func Print1[int](input int) {fmt.Println(input)
}func Print2[string](input string) {fmt.Println(input)
}
执行的大概示意图如下所示:
泛型函数 Print[T]
+-----------------+
| 泛型代码 |
+-----------------+|Monomorphization(为不同值类型生成副本)|+------------+| PrintInt() | // 为 int 类型生成的函数副本+------------+| PrintStr() | // 为 string 类型生成的函数副本+------------+| PrintF64() | // 为 float64 类型生成的函数副本+------------+
Go的泛型使用了多种方式,上面描述的属于其中的一种方式,也就是单态化,这种方式主要应用于参数是值的函数。
上面我们提到的这种方式虽然简单,但是如果函数副本太多的话,最终编译出来的二进制文件肯定是很大的,所以还采用了虚拟方法表的方式。
当泛型函数接收的是指针类型或接口类型时,编译器会为它生成一个字典表。这个表类似于虚拟方法表,记录了如何在运行时处理不同类型的操作。
我们可以通过下面的示意图来理解:
编译时:
+--------------------------------------------+
| 编译器检查到 Person 实现了 Stringer 接口 |
+--------------------------------------------+|v
+-----------------------------------------+
| 生成 Person 的虚拟方法表(VMT) |
| 包含 String() 指向 Person.String 的指针 |
+-----------------------------------------+运行时:
+-------------------------------------+
| 调用 PrintString(p) |
+-------------------------------------+|v
+---------------------------+
| 查找 p 的虚拟方法表 | --> 找到 Person.String() 方法
+---------------------------+|v
+----------------------+
| 调用 Person.String() |
+----------------------+
虚拟方法表比较抽象,我们以一句话理解就可以:调用的时候,会去查找PrintString(p)中p的方法表,最终找到了Person.String(),这时候就直接执行就可以了。
Go语言的泛型实现还在持续优化中,我们可以持续关注,现阶段掌握泛型的使用即可。
小结
本节我们讲解了泛型的基础概念、使用以及简单的实现原理。泛型为Go语言带来了更大的灵活性,帮助开发者编写更具通用性的代码。
在框架开发、工具开发场景应用比较广泛,通过泛型我们可以简单的将代码合并优化。
我的GitHub:https://github.com/swxctx
书籍地址:https://d.golang.website/
书籍代码:https://github.com/YouCanGolang/GoDeeperCode
相关文章:
Go进阶概览 -【7.2 泛型的使用与实现分析】
7.2 泛型的使用与实现分析 泛型是Go 1.18引入的概念,在引入这个概念前经过了好几年的考量最终才将这这个特性加进去。 泛型在多种语言中都是存在的,比如C、Java等语言中都有泛型的概念。 本节我们将针对泛型的使用、实现原理进行整体的讲解。 本节代…...
罗德岛战记游戏源码(客户端+服务端+数据库+全套源码)游戏大小9.41G
罗德岛战记游戏源码(客户端服务端数据库全套源码)游戏大小9.41G 下载地址: 通过网盘分享的文件:【源码】罗德岛战记游戏源码(客户端服务端数据库全套源码)游戏大小9.41G 链接: https://pan.baidu.com/s/1y0…...
AI+教育|拥抱AI智能科技,让课堂更生动高效
AI在教育领域的应用正逐渐成为现实,提供互动性强的学习体验,正在改变传统教育模式。AI不仅改变了传统的教学模式,还为教育提供了更多的可能性和解决方案。从个性化学习体验到自动化管理任务,AI正在全方位提升教育质量和效率。随着…...
WebServer
一、服务器代码 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <unistd.h> #define PORT 80 #define BUFFER_SIZE 1024 void ha…...
java项目之基于spring boot的多维分类的知识管理系统的设计与实现源码
项目简介 基于spring boot的多维分类的知识管理系统的设计与实现实现了以下功能: 基于spring boot的多维分类的知识管理系统的设计与实现的主要使用者管理员可以管理用户信息,知识分类,知识信息等,用户可以查看和下载管理员发布…...
go的结构体、方法、接口
结构体: 结构体:不同类型数据集合 结构体成员是由一系列的成员变量构成,这些成员变量也被称为“字段” 先声明一下我们的结构体: type Person struct {name stringage intsex string } 定义结构体法1: var p1 P…...
力扣第一题——删除有序数组中的重复项
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次,返回删除后数组的新长度。不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1)额外空间的条件下完成。 示例 1&#x…...
Tuxera NTFS for Mac 2023绿色版
在数字化时代,数据的存储和传输变得至关重要。Mac用户经常需要在Windows NTFS格式的移动硬盘上进行读写操作,然而,由于MacOS系统默认不支持NTFS的写操作,这就需要我们寻找一款高效的读写软件。Tuxera NTFS for Mac 2023便是其中…...
LeetCode[中等] 155. 最小栈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。void push(int val) 将元素val推入堆栈。void pop() 删除堆栈顶部的元素。int top() 获取堆栈顶部的元素。int get…...
Python青少年简明教程目录
Python青少年简明教程目录 学习编程语言时,会遇到“开头难”和“深入难”的问题,这是许多编程学习者都会经历的普遍现象。 学习Python对于青少年来说是一个很好的编程起点,相对容易上手入门,但语言特性复杂,应用较广&…...
Revit学习记录-版本2018【持续补充】
将墙面拆分并使用不同材料 【修改】>【几何图形】>【拆分面】(SF), 然后再使用【修改】>【几何图形】>【填色】(PT)进行填充 楼板漏在墙外 绘制楼板时最好选择墙体绘制,如果标高上不显示墙体,可以先选…...
深度学习01-概述
深度学习是机器学习的一个子集。机器学习是实现人工智能的一种途径,而深度学习则是通过多层神经网络模拟人类大脑的方式进行学习和知识提取。 深度学习的关键特点: 1. 自动提取特征:与传统的机器学习方法不同,深度学习不需要手动…...
leetcode232. 用栈实现队列
leetcode232. 用栈实现队列 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty): 实现 MyQueue 类: void push(int x) 将元素 x 推到队列的末尾 int pop() 从队列的开头移除并返回元…...
智慧火灾应急救援航拍检测数据集(无人机视角)
智慧火灾应急救援。 无人机,直升机等航拍视角下火灾应急救援检测数据集,数据分别标注了火,人,车辆这三个要素内容,29810张高清航拍影像,共31GB,适合森林防火,应急救援等方向的学术研…...
eureka.client.service-url.defaultZone的坑
错误的配置 eureka: client: service-url: default-zone: http://192.168.100.10:8080/eureka正确的配置 eureka: client: service-url: defaultZone: http://192.168.100.10:8080/eureka根据错误日志堆栈打断电调试 出现两个key,也就是defaultZone不支持snake-c…...
统信服务器操作系统【d版字符系统升级到dde图形化】配置方法
统信服务器操作系统d版本上由字符系统升级到 dde 桌面系统的过程 文章目录 一、准备环境二、功能描述安装步骤1. lightdm 安装2. dde 安装 一、准备环境 适用版本:■UOS服务器操作系统d版 适用架构:■ARM64、AMD64、MIPS64 网络:连接互联网…...
学习IEC 62055付费系统标准
1.IEC 62055 国际标准 IEC 62055 是目前关于付费系统的唯一国际标准,涵盖了付费系统、CIS 用户信息系统、售电系统、传输介质、数据传输标准、预付费电能表以及接口标准等内容。 IEC 62055-21 标准化架构IEC 62055-31 1 级和 2 级有功预付费电能表IEC 62055-41 STS…...
如何在Markdown写文章上传到wordpress保证图片不丢失
如何在Markdown写文章上传到wordpress保证图片不丢失 写文日期,2023-11-16 引文 众所周知markdown是一款nb的笔记软件,本篇文章讲解如何在markdown编写文件后上传至wordpress论坛。并且保证图片不丢失(将图片上传至云端而非本地方法) 一&…...
html,css基础知识点笔记(二)
9.18(二) 本文主要教列表的样式设计 1)文本溢出 效果图 文字限制一行显示几个字,多余打点 line-height: 1.8em; white-space: nowrap; width: 40em; overflow: hidden; text-overflow: ellipsis;em表示一个文字的大小单位&…...
(k8s)kubernetes 部署Promehteus学习之路
整个Prometheus生态包含多个组件,除了Prometheus server组件其余都是可选的 Prometheus Server:主要的核心组件,用来收集和存储时间序列数据。 Client Library::客户端库,为需要监控的服务生成相应的 metrics 并暴露给…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
