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

Go - 泛型的使用

泛型的语法

泛型为Go语言添加了三个新的重要特性:

  1. 函数和类型的类型参数
  2. 将接口类型定义为类型集,包括没有方法的类型。
  3. 类型推断,它允许在调用函数时在许多情况下省略类型参数。

类型参数

类型参数的使用

除了函数中支持类型参数列表外,类型也支持类型参数列表

单个类型参数
type Slice[T int | string] []T 
多个类型参数
type Map[K int | string, V float32 | float64] map[K]V

不同泛型参数之间使用,隔开。

泛型类型使用方法
type Tree[T interface{}] struct {left, right *Tree[T]value       T
}// 同时,泛型类型可以使用用*方法*。
func (t *Tree[T]) Lookup(x T) *Tree[T] { ... }

在上述泛型中,TKV 都属于类型形参,类型形参后面是类型约束

需要注意的是:
要使用泛型类型,必须先对其实例化

var stringTree Tree[string]

类型实例化

定义一个适用于一组类型的min函数

func min(T int | float64)(a,b T) T {if a<b{return a}return b
}

进行类型实例化

// 1. int
m1 := min[int](1,2)
// 相当于
// m1 := min[int]
// mi := m1(1,2)// 2. float
m2 := min[float64](-0.1,-0.2)
// 相当于
// 同上

根据上面的例子,
在定义函数时:
(T int | float64) 就是形参
在调用函数时:
m1 := min[int] 是在进行类型实例化mi := m1(1,2) 则是在调用。

类型实例化分为两步:

  1. 首先,编译器在整个泛型函数或类型中将所有类型形参(type parameters)替换为它们各自的类型实参(type arguments)。
  2. 其次,编译器验证每个类型参数是否满足相应的约束。

即先“替换”,再“验证”。

类型约束

Go语言中的类型约束是接口类型
类型参数列表中每个类型参数都有一个类型约束,它定义了一个类型集,只有在这个类型集中的类型才能用作类型实参。
类型约束常见有两种方式:

  1. 类型约束接口直接在类型参数列表中使用
// 类型约束字面量,通常外层interface{}可省略
func min[T interface{ int | float64 }](a, b T) T {if a <= b {return a}return b
}
  1. 作为类型约束使用的接口类型可以事先定义并支持复用
// 事先定义好的类型约束类型
type Value interface {int | float64
}
func min[T Value](a, b T) T {if a <= b {return a}return b
}

若省略外层interface{}会引起歧义,则不能省略。

type IntPtrSlice [T *int] []T  // T*int ?type IntPtrSlice[T *int,] []T  // 只有一个类型约束时可以添加`,`
type IntPtrSlice[T interface{ *int }] []T // 使用interface{}包裹

类型集

**Go1.18开始接口类型的定义也发生了改变,由过去的接口类型定义方法集(method set)变成了接口类型定义类型集(type set)。**也就是说,接口类型现在可以用作值的类型,也可以用作类型约束。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

把接口类型当做类型集相较于方法集有一个优势: 我们可以显式地向集合添加类型,从而以新的方式控制类型集。

Go语言扩展了接口类型的语法,让我们能够向接口中添加类型。例如

type V interface {int | string | bool
}

上面的代码就定义了一个包含 intstringbool 类型的类型集。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
从 Go 1.18 开始,一个接口不仅可以嵌入其他接口,还可以嵌入任何类型、类型的联合或共享相同底层类型的无限类型集合。

当用作类型约束时,由接口定义的类型集精确地指定允许作为相应类型参数的类型。

  • |符号
    T1 | T2表示类型约束为T1和T2这两个类型的并集
type Integer interface {Signed | Unsigned
}
  • ~符号
    ~T表示所以底层类型是T的类型,例如~string表示所有底层类型是string的类型集合。
type MyString string // MyString的底层类型是string

通过[T ~string] 就可以限制泛型参数的底层类型必须为string

注意:~符号后面只能是基本类型。

any接口

空接口在类型参数列表中很常见,在Go 1.18引入了一个新的预声明标识符,作为空接口类型的别名。

// src/builtin/builtin.gotype any = interface{}

由此,我们可以使用如下代码:

func foo[S ~[]E, E any]() {// ...
}

类型判断

函数参数类型判断

func min[T int | float64](a, b T) T {if a <= b {return a}return b
}

类型形参T用于指定ab的类型。我们可以使用显式类型实参调用它:

var a, b, m float64
m = min[float64](a, b) // 显式指定类型实参

在许多情况下,编译器可以从普通参数推断 T 的类型实参。这使得代码更短,同时保持清晰。

var a, b, m float64m = min(a, b) // 无需指定类型实参

这种从实参的类型推断出函数的类型实参的推断称为函数实参类型推断
其只适用于函数参数中使用的类型参数,而不适用于仅在函数结果中或仅在函数体中使用的类型参数。

例如,它不适用于像 MakeT [ T any ]() T 这样的函数,因为它只使用 T 表示结果。

类型约束推断

Go 语言支持另一种类型推断,即约束类型推断

// Scale 返回切片中每个元素都乘c的副本切片
func Scale[E constraints.Integer](s []E, c E) []E {r := make([]E, len(s))for i, v := range s {r[i] = v * c}return r
}

这是一个泛型函数适用于任何整数类型的切片。

现在假设我们有一个多维坐标的 Point 类型,其中每个 Point 只是一个给出点坐标的整数列表。这种类型通常会实现一些业务方法,这里假设它有一个String方法。

type Point []int32func (p Point) String() string {b, _ := json.Marshal(p)return string(b)
}

由于一个Point其实就是一个整数切片,我们可以使用前面编写的Scale函数:

func ScaleAndPrint(p Point) {r := Scale(p, 2)fmt.Println(r.String()) // 编译失败
}

不幸的是,这代码会编译失败,输出r.String undefined (type []int32 has no field or method String的错误。

问题是Scale函数返回类型为[]E的值,其中E是参数切片的元素类型。当我们使用Point类型的值调用Scale(其基础类型为[]int32)时,我们返回的是[]int32类型的值,而不是Point类型。这源于泛型代码的编写方式,但这不是我们想要的。

为了解决这个问题,我们必须更改 Scale 函数,以便为切片类型使用类型参数。

func Scale[S ~[]E, E constraints.Integer](s S, c E) S {r := make(S, len(s))for i, v := range s {r[i] = v * c}return r
}

现在这个Scale函数,不仅支持传入普通整数切片参数,也支持传入Point类型参数。

编译器推断 E 的类型参数是切片的元素类型的过程称为约束类型推断

约束类型推断从类型参数约束推导类型参数。当一个类型参数具有根据另一个类型参数定义的约束时使用。当其中一个类型参数的类型参数已知时,约束用于推断另一个类型参数的类型参数。

总结

总之,如果你发现自己多次编写完全相同的代码,而这些代码之间的唯一区别就是使用的类型不同,这个时候你就应该考虑是否可以使用类型参数。

泛型和接口类型之间并不是替代关系,而是相辅相成的关系。泛型的引入是为了配合接口的使用,让我们能够编写更加类型安全的Go代码,并能有效地减少重复代码。

参考文章:
https://www.liwenzhou.com/posts/Go/generics/#c-0-3-1

相关文章:

Go - 泛型的使用

泛型的语法 泛型为Go语言添加了三个新的重要特性: 函数和类型的类型参数。将接口类型定义为类型集&#xff0c;包括没有方法的类型。类型推断&#xff0c;它允许在调用函数时在许多情况下省略类型参数。 类型参数 类型参数的使用 除了函数中支持类型参数列表外&#xff0c…...

蓝桥杯刷题-dp-线性dp(守望者的逃离,摆花,线段)

[NOIP 2007 普及组] 守望者的逃离 题目描述 恶魔猎手尤迪安野心勃勃&#xff0c;他背叛了暗夜精灵&#xff0c;率领深藏在海底的娜迦族企图叛变。 守望者在与尤迪安的交锋中遭遇了围杀&#xff0c;被困在一个荒芜的大岛上。 为了杀死守望者&#xff0c;尤迪安开始对这个荒岛…...

内容中台的企业CMS架构是什么?

企业CMS模块化架构 现代企业内容管理系统的核心在于模块化架构设计&#xff0c;通过解耦内容生产、存储、发布等环节构建灵活的技术栈。动态/静态发布引擎整合技术使系统既能处理实时更新的产品文档&#xff0c;也能生成高并发的营销落地页&#xff0c;配合版本控制机制确保内…...

算法题(81):询问学号

审题&#xff1a; 需要我们根据给出的n值确定录入数据个数&#xff0c;然后根据给出的数据存储学号。再根据m值确定需要输出的学号个数&#xff0c;然后根据数组内容输出学号 思路: 我们可以利用数组进行数据顺序存储&#xff0c;以及随机读取完成本题 由于学号最大为1e9&#…...

React antd的datePicker自定义,封装成组件

一、antd的datePicker自定义 需求&#xff1a;用户需要为日期选择器的每个日期单元格添加一个Tooltip&#xff0c;当鼠标悬停时显示日期、可兑换流量余额和本公会可兑流量。这些数据需要从接口获取。我需要结合之前的代码&#xff0c;确保Tooltip正确显示&#xff0c;并且数据…...

C++ AVL树详解(含模拟实现)

目录 AVL树的概念 AVL树节点的定义 AVL树的插入 AVL树的旋转&#xff08;难点&#xff09; AVL树的验证 AVL树的删除(本文不做具体的模拟实现) AVL树的性能 AVL树的模拟实现 AVL树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索…...

Spring Boot 3.x 系列【3】Spring Initializr快速创建Spring Boot项目

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Spring Boot版本3.0.3 源码地址&#xff1a;https://gitee.com/pearl-organization/study-spring-boot3 文章目录 前言安装JDK 17创建Spring Boot 项目 方式1&#xff1a;网页在线生成方式2&#…...

Elasticsearch:过滤 HNSW 搜索,快速模式

作者&#xff1a;来自 Elastic Benjamin Trent 通过我们的 ACORN-1 算法实现&#xff0c;探索我们对 Apache Lucene 中的 HNSW 向量搜索所做的改进。 多年来&#xff0c;Apache Lucene 和 Elasticsearch 一直支持使用 kNN 查询的过滤搜索&#xff0c;允许用户检索符合指定元数据…...

TCP长连接与短连接

TCP长连接与短连接 TCP&#xff08;传输控制协议&#xff09;中的长连接和短连接是两种不同的连接管理方式&#xff0c;各有优缺点&#xff1a; 短连接 短连接是指客户端与服务器完成一次数据交换后就断开连接。下次需要通信时&#xff0c;再重新建立连接。 特点&#xff1…...

【AI测试学习】AnythingLLM+Ollama+DeepSeek部署私人知识库

1.搭建DeepSeek大语言模型 1.1Ollama大预言模型部署 Ollama简化了大型语言模型的运行,让每个人都能在本地轻松体验AI的强大,打开浏览器-下载Ollama-输入命令-搞定,这是本地部署大语言模型的全新方式。 这里我们借助Ollama大预言模型部署工具进行搭建 官网如下:Ollama …...

防流、节抖、重绘、回流原理,以及实现方法和区别

防流、节抖、重绘、回流原理&#xff0c;以及实现方法和区别&#xff0c;还有就是为什么会出现这种情况&#xff1f; 防抖&#xff08;Debounce&#xff09; 原理 防抖就像是你坐电梯&#xff0c;如果你一直不停地按开门按钮&#xff0c;电梯不会每次都开门&#xff0c;而是…...

通义灵码插件安装入门教学 - IDEA(安装篇)

在开发过程中&#xff0c;使用合适的工具和插件可以极大地提高我们的工作效率。今天&#xff0c;我们将详细介绍如何在 IntelliJ IDEA 中安装并配置通义灵码插件&#xff0c;这是一款旨在提升开发者效率的实用工具。无论你是新手还是有经验的开发者&#xff0c;本文都将为你提供…...

ES、OAS、ERP、电子政务、企业信息化(高软35)

系列文章目录 ES、OAS、ERP、电子政务、企业信息化 文章目录 系列文章目录前言一、专家系统&#xff08;ES&#xff09;二、办公自动化系统&#xff08;OAS&#xff09;三、企业资源规划&#xff08;ERP&#xff09;四、典型信息系统架构模型1.政府信息化和电子政务2.企业信息…...

用大白话解释缓存Redis +MongoDB是什么有什么用怎么用

Redis和MongoDB是什么&#xff1f; Redis&#xff1a;像你家的“小冰箱”&#xff0c;专门存高频使用的食物&#xff08;数据&#xff09;。它是基于内存的键值数据库&#xff0c;读写速度极快&#xff08;每秒超10万次操作&#xff09;。比如你每次打开手机App&#xff0c;用…...

华为数通Datacom认证体系详解:从HCIA到HCIE的进阶路径

华为数通Datacom&#xff08;Data Communication&#xff09;课程是华为认证体系中的核心方向之一&#xff0c;聚焦企业网络通信与数据通信技术&#xff0c;适合从事网络规划、部署和运维的人员。 一、数通Datacom课程体系 华为数通Datacom认证分为 三个级别&#xff0c;逐级递…...

PyTorch 的 nn.NLLLoss:负对数似然损失全解析

PyTorch 的 nn.NLLLoss&#xff1a;负对数似然损失全解析 在 PyTorch 的损失函数家族中&#xff0c;nn.NLLLoss&#xff08;Negative Log Likelihood Loss&#xff0c;负对数似然损失&#xff09;是一个不太起眼但非常重要的成员。它经常跟 LogSoftmax 搭配出现&#xff0c;尤…...

《数据安全架构设计与实战》的目录

《数据安全架构设计与实战》的目录如下1&#xff1a; 第一部分 安全架构基础 第 1 章 架构第 2 章 安全架构 2.1 什么是安全2.2 为什么使用 “数据安全” 这个术语2.3 什么是安全架构2.4 安全架构 5A 方法论2.5 安全架构 5A 与 CIA 的关系 第二部分 产品安全架构 …...

python-leetcode-删除并获得点数

740. 删除并获得点数 - 力扣&#xff08;LeetCode&#xff09; 解法 1&#xff1a;动态规划&#xff08;O(n) 时间&#xff0c;O(n) 空间&#xff09; class Solution:def deleteAndEarn(self, nums: List[int]) -> int:if not nums:return 0# 统计每个数的贡献points Cou…...

助力DeepSeek私有化部署服务:让企业AI落地更简单、更安全

在数字化转型的浪潮中&#xff0c;越来越多的企业选择私有化部署AI技术&#xff0c;以保障数据安全、提升业务效率并实现自主可控。DeepSeek作为行业领先的AI开源技术&#xff0c;其技术可以支持企业私有化部署&#xff0c;企业需要一站式服务私有化部署&#xff0c;涵盖硬件采…...

Android JNI开发指南

了解Android JNI开发。首先&#xff0c;我需要回忆一下JNI的基本概念和作用。JNI允许Java代码与本地代码&#xff08;如C/C&#xff09;交互&#xff0c;这在需要高性能或者使用现有C/C库时非常有用。 接下来&#xff0c;我应该整理开发步骤。用户可能想知道如何开始&#xff…...

【每天认识一个漏洞】url重定向

&#x1f31d;博客主页&#xff1a;菜鸟小羊 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 | 每天学会一个渗透测试工具 常见应用场景 主要是业务逻辑中需要进行跳转的地方。比如登录处、注册处、访问用户信息、订单信息、加入购物车、分享、收…...

纯代码实战--用Deepseek+SQLite+Ollama搭建数据库助手

如何用Python调用本地模型实现DeepSeek提示词模板&#xff1a;一步步教你高效解决13种应用场景 从零到一&#xff1a;纯代码联合PyQt5、Ollama、Deepseek打造简易版智能聊天助手 用外接知识库武装大模型&#xff1a;基于Deepseek、Ollama、LangChain的RAG实战解析 纯代码实战–…...

2025 最新版鸿蒙 HarmonyOS 开发工具安装使用指南

为保证 DevEco Studio 正常运行&#xff0c;建议电脑配置满足如下要求&#xff1a; Windows 系统 操作系统&#xff1a;Windows10 64 位、Windows11 64 位内存&#xff1a;16GB 及以上硬盘&#xff1a;100GB 及以上分辨率&#xff1a;1280*800 像素及以上 macOS 系统 操作系统…...

日期时间 API

日期时间 API (java.time 包)&#xff0c;旨在解决旧版 java.util.Date 和 java.util.Calendar 存在的一些设计缺陷&#xff0c;比如线程不安全、时区处理不一致等问题。新 API 基于 ISO 8601 标准&#xff0c;更加直观、简洁&#xff0c;且支持时区和区域设置。主要类有&#…...

AI数字人开发,引领科技新潮流

引言 随着人工智能技术的迅猛发展&#xff0c;AI 数字人在影视娱乐、客户服务、教育及医疗等多个领域展现出巨大的潜力。本文旨在为开发者提供一份详细的 AI 数字人系统开发指南&#xff0c;涵盖从基础架构到实现细节的各个方面&#xff0c;包括人物建模、动作生成、语音交互、…...

领域驱动设计:事件溯源架构简介

概述 事件溯源架构通常由3种应用设计模式组成,分别是:事件驱动(Event Driven),事件溯源(Event Source)、CQRS(读写分离)。这三种应用设计模式常见于领域驱动设计(DDD)中,但它们本身是一种应用设计的思想,不仅仅局限于DDD,每一种模式都可以单独拿出来使用。 E…...

自定义类加载器国密版本冲突

自定义类加载器国密版本冲突 对接三方接口经常使用到国密加密包&#xff08;bcprov&#xff09;&#xff0c;此时系统已经引入了1.5版本&#xff0c;而三方提供的sdk中引用了1.6版版本&#xff0c;两个版本有冲突&#xff0c;如果系统加载到1.5版本的将会加密异常(各种奇怪的异…...

‌Debian 包版本号比较规则详解

1 版本号组成结构 Debian 版本号格式为&#xff1a;[epoch:]upstream_version[-debian_revision] 示例‌&#xff1a;2:1.18.3~betadfsg1-5b1 组件说明比较优先级‌Epoch‌冒号前的数字 (2:)最高‌Upstream‌主版本 (1.18.3~betadfsg1)中‌Debian修订号‌减号后的部分 (5)最…...

STM32之影子寄存器

预分频寄存器计数到一半的时候&#xff0c;改变预分频值&#xff0c;此时不会立即生效&#xff0c;会等到计数完成&#xff0c;再从影子寄存器即预分频缓冲器里装载修改的预分频值。 如上图&#xff0c;第一行是内部时钟72M&#xff0c;第二行是时钟使能&#xff0c;高电平启动…...

x64汇编下过程参数解析

简介 好久没上博客, 突然发现我的粉丝数变2700了, 真是这几个月涨的粉比我之前好几年的都多, 于是心血来潮来写一篇, 记录一下x64下的调用约定(这里的调用约定只针对windows平台) Windows下的x64程序的调用约定有别于x86下的"stdcall调用约定"以及"cdecl调用约…...