Go - 泛型的使用

news2025/5/18 5:45:47

泛型的语法

泛型为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.go

type 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 float64

m = 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 []int32

func (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

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

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

相关文章

蓝桥杯刷题-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;允许用户检索符合指定元数据…

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

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

通义灵码插件安装入门教学 - 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.企业信息…

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;涵盖硬件采…

【每天认识一个漏洞】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 系统 操作系统…

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

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

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

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

STM32之影子寄存器

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

x64汇编下过程参数解析

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

Blender调整最佳渲染清晰度

1.渲染采样调高 512 2.根据需要 开启AO ,开启辉光 , 开启 屏幕空间反射 3.调高分辨率 4096x4096 100% 分辨率是清晰度的关键 , 分辨率不高 , 你其他参数调再高都没用 4.世界环境开启体积散射 , 可以增强氛围感 5.三点打光法 放在模型和相机45夹角上 白模 白模带线条 成品