当前位置: 首页 > 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 并暴露给…...

第19节 Node.js Express 框架

Express 是一个为Node.js设计的web开发框架&#xff0c;它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用&#xff0c;和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

微信小程序之bind和catch

这两个呢&#xff0c;都是绑定事件用的&#xff0c;具体使用有些小区别。 官方文档&#xff1a; 事件冒泡处理不同 bind&#xff1a;绑定的事件会向上冒泡&#xff0c;即触发当前组件的事件后&#xff0c;还会继续触发父组件的相同事件。例如&#xff0c;有一个子视图绑定了b…...

Axios请求超时重发机制

Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式&#xff1a; 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…...

2025季度云服务器排行榜

在全球云服务器市场&#xff0c;各厂商的排名和地位并非一成不变&#xff0c;而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势&#xff0c;对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析&#xff1a; 一、全球“三巨头”…...

人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent

安全大模型训练计划&#xff1a;基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标&#xff1a;为安全大模型创建高质量、去偏、符合伦理的训练数据集&#xff0c;涵盖安全相关任务&#xff08;如有害内容检测、隐私保护、道德推理等&#xff09;。 1.1 数据收集 描…...

mac:大模型系列测试

0 MAC 前几天经过学生优惠以及国补17K入手了mac studio,然后这两天亲自测试其模型行运用能力如何&#xff0c;是否支持微调、推理速度等能力。下面进入正文。 1 mac 与 unsloth 按照下面的进行安装以及测试&#xff0c;是可以跑通文章里面的代码。训练速度也是很快的。 注意…...

Linux-进程间的通信

1、IPC&#xff1a; Inter Process Communication&#xff08;进程间通信&#xff09;&#xff1a; 由于每个进程在操作系统中有独立的地址空间&#xff0c;它们不能像线程那样直接访问彼此的内存&#xff0c;所以必须通过某种方式进行通信。 常见的 IPC 方式包括&#…...