当前位置: 首页 > news >正文

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")

在上面的代码中,我们通过泛型仅定义了一个函数,即实现了传递intstring参数的目的。

定义的格式也是固定的: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]这样的格式。

那么这种格式是什么意思呢?如果我们这样写,其实就代表这个函数接收的参数只能是intfloat64两种类型的。

基于我们函数的功能,如果传入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必须是支持比较操作的类型,例如整数、字符串等。

    comparableGo的内置接口,用于表示可以比较的类型(支持 == != 操作)。

  • 自定义接口作为约束

    // 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")

在上面的代码中,我们实现了一个简单的打印函数,调用的时候传入了intstring类型的数据。

那么在我们编译的时候,编译器可能会生成下面的代码:

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的多维分类的知识管理系统的设计与实现实现了以下功能&#xff1a; 基于spring boot的多维分类的知识管理系统的设计与实现的主要使用者管理员可以管理用户信息&#xff0c;知识分类&#xff0c;知识信息等&#xff0c;用户可以查看和下载管理员发布…...

go的结构体、方法、接口

结构体&#xff1a; 结构体&#xff1a;不同类型数据集合 结构体成员是由一系列的成员变量构成&#xff0c;这些成员变量也被称为“字段” 先声明一下我们的结构体&#xff1a; type Person struct {name stringage intsex string } 定义结构体法1&#xff1a; var p1 P…...

力扣第一题——删除有序数组中的重复项

给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使得出现次数超过两次的元素只出现两次&#xff0c;返回删除后数组的新长度。不要使用额外的数组空间&#xff0c;你必须在 原地 修改输入数组 并在使用 O(1)额外空间的条件下完成。 示例 1&#x…...

Tuxera NTFS for Mac 2023绿色版

​ 在数字化时代&#xff0c;数据的存储和传输变得至关重要。Mac用户经常需要在Windows NTFS格式的移动硬盘上进行读写操作&#xff0c;然而&#xff0c;由于MacOS系统默认不支持NTFS的写操作&#xff0c;这就需要我们寻找一款高效的读写软件。Tuxera NTFS for Mac 2023便是其中…...

LeetCode[中等] 155. 最小栈

设计一个支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。void push(int val) 将元素val推入堆栈。void pop() 删除堆栈顶部的元素。int top() 获取堆栈顶部的元素。int get…...

Python青少年简明教程目录

Python青少年简明教程目录 学习编程语言时&#xff0c;会遇到“开头难”和“深入难”的问题&#xff0c;这是许多编程学习者都会经历的普遍现象。 学习Python对于青少年来说是一个很好的编程起点&#xff0c;相对容易上手入门&#xff0c;但语言特性复杂&#xff0c;应用较广&…...

Revit学习记录-版本2018【持续补充】

将墙面拆分并使用不同材料 【修改】>【几何图形】>【拆分面】(SF)&#xff0c; 然后再使用【修改】>【几何图形】>【填色】&#xff08;PT&#xff09;进行填充 楼板漏在墙外 绘制楼板时最好选择墙体绘制&#xff0c;如果标高上不显示墙体&#xff0c;可以先选…...

深度学习01-概述

深度学习是机器学习的一个子集。机器学习是实现人工智能的一种途径&#xff0c;而深度学习则是通过多层神经网络模拟人类大脑的方式进行学习和知识提取。 深度学习的关键特点&#xff1a; 1. 自动提取特征&#xff1a;与传统的机器学习方法不同&#xff0c;深度学习不需要手动…...

leetcode232. 用栈实现队列

leetcode232. 用栈实现队列 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作&#xff08;push、pop、peek、empty&#xff09;&#xff1a; 实现 MyQueue 类&#xff1a; void push(int x) 将元素 x 推到队列的末尾 int pop() 从队列的开头移除并返回元…...

智慧火灾应急救援航拍检测数据集(无人机视角)

智慧火灾应急救援。 无人机&#xff0c;直升机等航拍视角下火灾应急救援检测数据集&#xff0c;数据分别标注了火&#xff0c;人&#xff0c;车辆这三个要素内容&#xff0c;29810张高清航拍影像&#xff0c;共31GB&#xff0c;适合森林防火&#xff0c;应急救援等方向的学术研…...

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&#xff0c;也就是defaultZone不支持snake-c…...

统信服务器操作系统【d版字符系统升级到dde图形化】配置方法

统信服务器操作系统d版本上由字符系统升级到 dde 桌面系统的过程 文章目录 一、准备环境二、功能描述安装步骤1. lightdm 安装2. dde 安装 一、准备环境 适用版本&#xff1a;■UOS服务器操作系统d版 适用架构&#xff1a;■ARM64、AMD64、MIPS64 网络&#xff1a;连接互联网…...

学习IEC 62055付费系统标准

1.IEC 62055 国际标准 IEC 62055 是目前关于付费系统的唯一国际标准&#xff0c;涵盖了付费系统、CIS 用户信息系统、售电系统、传输介质、数据传输标准、预付费电能表以及接口标准等内容。 IEC 62055-21 标准化架构IEC 62055-31 1 级和 2 级有功预付费电能表IEC 62055-41 STS…...

如何在Markdown写文章上传到wordpress保证图片不丢失

如何在Markdown写文章上传到wordpress保证图片不丢失 写文日期,2023-11-16 引文 众所周知markdown是一款nb的笔记软件&#xff0c;本篇文章讲解如何在markdown编写文件后上传至wordpress论坛。并且保证图片不丢失&#xff08;将图片上传至云端而非本地方法&#xff09; 一&…...

html,css基础知识点笔记(二)

9.18&#xff08;二&#xff09; 本文主要教列表的样式设计 1&#xff09;文本溢出 效果图 文字限制一行显示几个字&#xff0c;多余打点 line-height: 1.8em; white-space: nowrap; width: 40em; overflow: hidden; text-overflow: ellipsis;em表示一个文字的大小单位&…...

(k8s)kubernetes 部署Promehteus学习之路

整个Prometheus生态包含多个组件&#xff0c;除了Prometheus server组件其余都是可选的 Prometheus Server&#xff1a;主要的核心组件&#xff0c;用来收集和存储时间序列数据。 Client Library:&#xff1a;客户端库&#xff0c;为需要监控的服务生成相应的 metrics 并暴露给…...

MySQL 8安装指南:Win/Mac/Linux全平台教程,含避坑技巧

一、MySQL 8 版本选择推荐下载 Oracle 官方版&#xff0c;开源、免费、更新最全。 &#x1f449; 官网下载地址&#xff1a; https://dev.mysql.com/downloads/mysql/&#x1fa9f; 二、Windows 安装步骤✅ 1️⃣ 下载 Installer访问官网链接 → 点击 "MySQL Community (G…...

抖音无水印批量下载终极指南:douyin-downloader免费神器

抖音无水印批量下载终极指南&#xff1a;douyin-downloader免费神器 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback sup…...

N_m3u8DL-CLI-SimpleG:一键下载M3U8视频的终极图形界面工具

N_m3u8DL-CLI-SimpleG&#xff1a;一键下载M3U8视频的终极图形界面工具 【免费下载链接】N_m3u8DL-CLI-SimpleG N_m3u8DL-CLIs simple GUI 项目地址: https://gitcode.com/gh_mirrors/nm3/N_m3u8DL-CLI-SimpleG 你是否曾经想要保存在线视频却因为复杂的M3U8格式而束手无…...

【机器学习】神经网络学习手册(四)损失函数

损失函数 Loss Function 用来衡量模型“错的有多离谱” 损失函数 模型预测值 vs 真实标签之间的差距 训练目标&#xff1a;找到一组权重&#xff0c;让损失函数的值最小化 - 损失越大 预测越差&#xff0c;需要优化 - 损失越小 预测越好&#xff0c;接近目标 常见的损失函数…...

轻松掌握华硕笔记本性能控制:轻量级替代工具的使用方法

轻松掌握华硕笔记本性能控制&#xff1a;轻量级替代工具的使用方法 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, Zenbook, E…...

C 语言通讯录(终版)|新手踩坑全总结 + 最终可运行代码博客简介

系列回顾 本系列三篇完整闭环&#xff1a; 第一篇&#xff08;基础版&#xff09;&#xff1a;从零实现增删查改 文件存储&#xff0c;踩遍新手所有坑&#xff08;格式符乱码、文件闪退、输入死循环&#xff09;&#xff1b;第二篇&#xff08;优化版&#xff09;&#xff1…...

如何找到最适合你的私有化IM?

跳出公有云的舒适区&#xff0c;决心搭建私有化IM&#xff0c;并不是一件能一蹴而就的事。市面上打着私有化旗号的软件鱼龙混杂&#xff0c;有的安装环境要求极高&#xff0c;有的功能华而不实。如何在复杂的选型迷雾中&#xff0c;找到最适合组织基因的那一款&#xff1f;你可…...

一文讲清WMS软件是什么?企业为什么要用WMS软件?

在数字化供应链时代&#xff0c;WMS软件&#xff08;仓储管理系统&#xff09;已成为企业物流管理的核心。面对仓库混乱、库存不准&#xff0c;很多企业都在问&#xff1a;WMS软件到底是什么&#xff1f;它和Excel或进销存有什么区别&#xff1f;企业为什么要用WMS软件&#xf…...

Gitea库完整从Ubuntu迁移到CentOS中

文章目录 一、概述 二、数据迁移 2.1 获取数据存储路径 2.2 搞事之前先备份(目标服务器CentOS) 2.2.1 停止gitea服务 2.2.2 备份gitea文件夹 2.3 从Ubuntu的数据目录中将数据拷贝到CentOS中 2.4 备份mysql数据库并拷贝到目标服务器(CentOS) 2.4.1 通过mysqldump备份数据库 …...

超越UNO:手把手教你为ESP8266和AVR单片机配置任意GPIO中断(附端口变化中断PCINT实战)

突破硬件限制&#xff1a;ESP8266与AVR单片机全引脚中断配置实战指南 在嵌入式开发中&#xff0c;中断处理是提升系统响应效率的核心技术。传统Arduino UNO仅提供2个专用外部中断引脚&#xff08;D2和D3&#xff09;&#xff0c;当项目需要同时监控多个传感器或按钮时&#xff…...