golang 泛型详解

目录

概念

~int vs .int

常见的用途和错误

结论:


概念

Go 在1.18 中添加了泛型,这样Go 就可以在定义时不定义类型,而是在使用时进行类型的定义,并且还可以在编译期间对参数类型进行校验。Go 目前只支持泛型方法,还不支持泛型接口,下面我会详细介绍Go的泛型使用以及常见的使用错误。其他概念性的东西我不想说太多,大家可以自己百度。

我们先看一个例子,很简单就是从map[string]int 提出所有的键的函数:

func getKeys(m map[string]int) []string {var keys []stringfor k := range m {keys = append(keys, k)}return keys
}

如果我们现在需要的是map[int]int,那么我们该怎么做呢?在使用泛型之前,Go 开发人员有几种选择:使用代码生成、反射或重复代码。例如,我们可以编写两个函数,每个函数对应一种 map 类型,或者甚至尝试扩展 getKeys 以接受不同的 map 类型:

func getKeys(m any) ([]any, error) {                      ❶switch t := m.(type) {default:return nil, fmt.Errorf("unknown type: %T", t)     ❷case map[string]int:var keys []anyfor k := range t {keys = append(keys, k)}return keys, nilcase map[int]string:// Copy the extraction logic}
}

❶ 接受和返回一个interface{}

❷如果不是我们需要的类型,返回一个错误

通过这个例子,我们注意到了一些问题。首先,它增加了模版代码。事实上我们想增加一个case 时,需要重复range 循环。同时函数现在接受任意类型,这意味着我们失去了GO作为类型化语言的一些优势。事实上,检查类型是否支持是在运行时而不是编译时进行的。因此如果类型不合法,我们还需要返回错误。最后,由于key 的类型是int或者字符串,我们必须返回interface{} 的切片。如果对切片进行处理,还需要断言操作。这种方法增加了调用方的工作量。现在有了泛型,我们可以重构这段代码。

下面是泛型的基本语法

类型参数是我们可以与函数和类型一起使用的通用类型。例如,以下函数接受一个类型参数:

func foo[T any](t T) {     ❶// ...
}

❶ T 是类型参数

在调用 foo 时,我们传递一个任意类型的类型参数。提供类型参数称为实例化,这项工作在编译时完成。这样,类型安全就成了核心语言特性的一部分,而 避免了运行时的开销。

让我们回到 getKeys 函数,使用类型参数编写一个通用版本,它可以接受任何类型的映射:

func getKeys[K comparable, V any](m map[K]V) []K {   ❶var keys []K                                     ❷for k := range m {keys = append(keys, k)}return keys
}

❶ K 是可以比较的,value 是 interface{}

❷ 创建一个K slice

我们这么定义的原因是map 的key 必须是可以比较的类型,不能是 any。比如我们不能用slices:

var m map[[]byte]int

这段代码会导致编译错误:映射键类型 []byte 无效。因此,我们不能接受任何键类型,而必须限制类型参数,使键类型符合特定要求。这里的要求是键类型必须是可比较的(我们可以使用 == 或 !=)。因此,我们将 K 定义为可比类型,而不是任意类型。

限制类型参数以满足特定要求称为约束。约束是一种接口类型,可以包含:一组行为(方法),任意类型 让我们举一个具体的例子来说明后者。假设我们不想接受任何可比较的映射键类型。例如,我们想将其限制为 int 或字符串类型。我们可以这样定义一个自定义约束:

type customConstraint interface {~int | ~string                   ❶
}
func getKeys[K customConstraint,     ❷V any](m map[K]V) []K {}

❶ 定义一个自定义类型,将类型限制为 int 和字符串 ❷ 将类型参数 K 改为自定义约束类型

首先,我们定义了一个 customConstraint 接口,使用联合运算符 | 将类型限制为 int 或字符串(稍后我们将讨论 ~ 的使用)。现在,K 是一个 customConstraint,而不是之前的可比类型。

getKeys 的签名强制要求我们可以使用任何值类型的 map 调用它,但键类型必须是 int 或字符串--例如,在调用方:

m = map[string]int{"one":   1,"two":   2,"three": 3,
}
keys := getKeys(m)

请注意,Go 可以推断出 getKeys 是以字符串类型参数调用的。之前的调用等同于此:

keys := getKeys[string](m)

~int vs .int

int 就是变量声明int 类型比如 var a int 而 ~int 指的是底层为int 类型,如 type b int , b 和 int 是两个不同的类型,但是b 的底层是int 类型

type customConstraint interface {~intString() string
}
type customInt int
​
func (i customInt) String() string {return strconv.Itoa(int(i))
}
func getKeys[keys customConstraint, v any](m map[keys]v) {}
func main() {t := customInt(1)getKeys(map[customInt]any{t: "a"})
}

由于 customInt 是一个 int 并实现了 String() 字符串方法,因此 customInt 类型满足已定义的约束。但是,如果我们将约束条件改为包含 int 而不是 ~int,那么使用 customInt 就会导致编译错误,因为 int 类型没有实现 String() 字符串。

到目前为止,我们已经讨论了在函数中使用泛型的示例。不过,我们也可以在数据结构中使用泛型。例如,我们可以创建一个包含任意类型值的链表。为此,我们将编写一个 Add 方法来追加一个节点:

type Node[T any] struct {                ❶Val  Tnext *Node[T]
}func (n *Node[T]) Add(next *Node[T]) {   ❷n.next = next
}

❶ 用一个类型参数

❷ 实例化一个类型接受者

在示例中,我们使用类型参数来定义 T,并在 Node 中使用这两个字段。关于方法,接收器是实例化的。事实上,由于 Node 是泛型的,它也必须遵循定义的类型参数。

关于类型参数,最后需要注意的一点是,它们不能与方法参数一起使用,只能与函数参数或方法接收器一起使用。例如,下面的方法将无法编译:

如果我们想在方法中使用泛型,那么接收器就必须是一个类型参数。现在,让我们来看看应该和不应该使用泛型的具体情况。

常见的用途和错误

讲到这里大家可能会问泛型如何使用呢,现在我们就讨论泛型几种常见的用途

  1. 数据结构--例如,如果我们实现的是二叉树、链表或堆,我们可以使用泛型来确定元素类型。

  2. 处理任何类型的切片、映射和通道的函数--例如,合并两个通道的函数可以处理任何类型的通道。因此,我们可以使用类型参数来确定通道类型:

比如在通道中我们对channel 类型进行限制了:

type chantype interface {int | string
}
​
func merge1[T chantype](ch1, ch2 <-chan T) <-chan T {// ...
}
  1. 例如,排序软件包包含一个 sort.Interface 接口,其中有三个方法:

    type Interface interface {Len() intLess(i, j int) boolSwap(i, j int)
    }

该接口被不同的函数使用,如 sort.Ints 或 sort .Float64。我们该怎么做呢?很多时候我们可能会想到用interface{} 抽象然后断言,用了泛型我们就可以提供一个模版,实例化什么参数由用户去决定了。

type SliceFn[T any] struct {S       []T  Compare func(T, T) bool
}
​
func (s SliceFn[T]) Len() int {return len(s.S)
}
​
func (s SliceFn[T]) Less(i, j int) bool {return s.Compare(s.S[i], s.S[j])
}
​
func (s SliceFn[T]) Swap(i, j int) {s.S[i], s.S[j] = s.S[j], s.S[i]
}
​
func main() {s := SliceFn[int]{S: []int{1, 2, 3},Compare: func(a int, b int) bool {return a > b},}sort.Sort(s)fmt.Println(s.S)
}

在本例中,通过分解行为,我们可以避免为每种类型创建一个函数。可以发现其实泛型比interface 更为抽象

结论:

先看一个例子:

func foo[T io.Writer](w T) {b := getBytes()_, _ = w.Write(b)
}

在调用参数类型的方法时--例如,考虑一个接收 io.Writer 并调用 Write 方法的函数:

在这种情况下,使用泛型不会给我们的代码带来任何价值。我们应该直接将 w 参数设为 io.Writer。 当泛型使我们的代码更复杂时--泛型从来不是强制性的,作为 Go 开发者,我们已经在没有泛型的情况下生活了十多年。如果我们在编写泛型函数或结构时发现它并没有让我们的代码变得更清晰,那么我们或许应该重新考虑我们在该特定用例中的决定。 虽然泛型在特定情况下会有所帮助,但我们应该谨慎对待何时使用、何时不使用泛型。一般来说,如果我们想回答何时不使用泛型,我们可以找到与何时不使用接口的相似之处。事实上,泛型引入了一种抽象形式,而我们必须记住,不必要的抽象会带来复杂性。 我们还是那句话,不要用不必要的抽象来污染我们的代码,现在还是专注于解决具体问题吧。这意味着我们不应过早使用类型参数。等到我们要编写模板代码时,再考虑使用泛型吧。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

书籍推荐|《使用 ESP32 开发物联网项目(第二版)》

随着物联网技术的迅猛发展&#xff0c;ESP32 因其强大的功能而备受物联网开发者的青睐。在此背景下&#xff0c;资深物联网专家 Vedat Ozan Oner 撰写的《使用 ESP32 开发物联网项目&#xff08;第二版&#xff09;》&#xff0c;为开发者提供了全面且深入的指南读物。 资深物…

Centos6安装PyTorch要求的更高版本gcc

文章目录 CentOS自带版本安装gcc 4的版本1. 获取devtoolset-8的yum源2. 安装gcc3. 版本检查和切换版本 常见问题1. 找不到包audit*.rpm包2. 找不到libcgroup-0.40.rc1-27.el6_10.x86_64.rpm 的包4. cc: fatal error: Killed signal terminated program cc1plus5. pybind11/pybi…

这一次,Python 真的有望告别 GIL 锁了?

Python 中有一把著名的锁——全局解释器锁&#xff08;Global Interpreter Lock&#xff0c;简写 GIL&#xff09;&#xff0c;它的作用是防止多个本地线程同时执行 Python 字节码&#xff0c;这会导致 Python 无法实现真正的多线程执行。&#xff08;注&#xff1a;本文中 Pyt…

用友 NC 23处接口XML实体注入漏洞复现

0x01 产品简介 用友 NC 是用友网络科技股份有限公司开发的一款大型企业数字化平台。 0x02 漏洞概述 用友 NC 多处接口存在XML实体注入漏洞,未经身份验证攻击者可通过该漏洞读取系统重要文件(如数据库配置文件、系统配置文件)、数据库配置文件等等,导致网站处于极度不安全…

2024全国乙卷高考文科数学:历年选择题真题和解析(2014~2023)

距离2024年高考还有三个多月的时间&#xff0c;今天我们来看一下2014~2023年全国乙卷高考文科数学的选择题&#xff0c;从过去十年的真题中随机抽取5道题&#xff0c;并且提供解析。后附六分成长独家制作的在线练习集&#xff0c;科学、高效地反复刷这些真题&#xff0c;吃透真…

安卓开发1- android stdio环境搭建

安卓开发1-android stdio环境搭建 Jdk环境搭建 1. 准备Jdk,这边已经准备好了jdk1.8.0,该文件直接使用即可 2. 系统变量添加 %JAVA_HOME%\bin JAVA_HOME 3. 系统变量&#xff0c;Path路径添加 4. 添加完成后&#xff0c;输入命令javac / java -version&#xff0c;验证环…

【数据结构】周末作业

1.new(struct list_head*)malloc(sizeof(struct list_head*)); if(newNULL) { printf("失败\n"); return; } new->nextprev->next; prev->nextnew; return; 2.struct list_head* pprev->next; prev->nextp->next; p->next->prevpr…

react-JSX基本使用

1.目标 能够知道什么是JSX 能够使用JSX创建React元素 能够在JSX中使用JS表达式 能够使用JSX的条件渲染和列表渲染 能够给JSX添加样式 2.目录 JSX的基本使用 JSX中使用JS表达式 JSX的条件渲染 JSX的列表渲染 JSX的样式处理 3.JSX的基本使用 3.1 createElement()的问题 A. …

程序员缺乏经验的 7 种表现!

程序员缺乏经验的 7 种表现&#xff01; 一次性提交大量代码 代码写的很烂 同时开展多项工作 性格傲慢 不能从之前的错误中学到经验 工作时间处理私人事务 盲目追逐技术潮流 知道这些表现&#xff0c;你才能在自己的程序员职业生涯中不犯相同的错误。 软件行业的工作经…

POST参数里加号+变成空格的问题处理

今天遇到个这样的问题&#xff0c;从前端传到后端的加密报文&#xff0c;里面包含了号&#xff0c;但在后端日志输出看出&#xff0c;变成空格。这个是由于经过RSA加密后引起的 解决办法&#xff1a; 1.前端转码&#xff1a;使用encodeURIComponent对参数进行转码 2.后端解码…

【web APIs】6、(学习笔记)有案例!

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、正则表达式正则基本使用元字符边界符量词范围字符类 二、替换和修饰符三、正则插件change 事件判断是否有类 四、案例举例学生就业信息表用户注册界面用户登…

项目流程图

实现便利店自助付款项目 服务器&#xff1a; 1、并发服务器&#xff08;多进程、多线程、IO多路复用&#xff09; 2、SQL数据库的创建和使用&#xff08;增删改查&#xff09; 3、以模块化编写项目代码&#xff0c;按照不同模块编写.h/.c文件 客户端&#xff1a; 1、QT客户端界…

Linux篇:指令

一 基本常识&#xff1a; 1. 文件文件内容文件的属性 2. 文件的操作对文件内容的操作对文件属性的操作 3. 文件的类型&#xff1a; d&#xff1a;目录文件 -&#xff1a;普通文件 4. 指令是可执行程序&#xff0c;指令的代码文件在系统的某一个位置存在的。/u…

区块链游戏解说:什么是 Arcade Champion

作者&#xff1a;lesleyfootprint.network 编译&#xff1a;cicifootprint.network 数据源&#xff1a;Arcade Champion Dashboard 什么是 Arcade Champion Arcade Champion 代表了移动游戏世界的重大革新。它将经典街机游戏的怀旧与创新元素结合在一起&#xff0c;包括 NF…

python脚本实现全景站点欧拉角转矩阵

效果 脚本 import numpy as np import math import csv import os from settings import *def euler_to_rotation_matrix(roll, pitch, yaw):# 计算旋转矩阵# Z-Y-X转换顺序Rz

groovy:XmlParser 读 Freeplane.mm文件,生成测试案例.csv文件

Freeplane 是一款基于 Java 的开源软件&#xff0c;继承 Freemind 的思维导图工具软件&#xff0c;它扩展了知识管理功能&#xff0c;在 Freemind 上增加了一些额外的功能&#xff0c;比如数学公式、节点属性面板等。 强大的节点功能&#xff0c;不仅仅节点的种类很多&#xff…

Unity | 动态读取C#程序集实现热更新

目录 一、动态语言 二、创建C#dll 1.VS中创建一个C#语言的库工程 2.添加UnityEngine.dll的依赖 3.编写代码&#xff0c;生成dll 三、Unity使用dll 一、动态语言 计算机编程语言可以根据它们如何将源代码转换为可以执行的代码来分类为静态语言和动态语言。 静态语言&…

力扣-跳跃游戏

问题 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 解答 class Solu…

工业设备生命周期的预测算法

设备生命周期的预测算法是一种运用数学模型和统计分析方法&#xff0c;对设备从投入使用到报废的整个生命周期进行预测的技术。这种预测对于资产管理、成本控制、维护计划制定等方面具有重要意义。常见的设备生命周期预测算法包括以下几种&#xff0c;希望对大家有所帮助。北京…

单点故障解决方案之Smart Link与Monitor Link

-SmartLink技术&#xff0c;创建Smart Link 组。在该组中&#xff0c;加入两个端口。其中1个端口是主端口&#xff0c;也称之为Master端口。另外1个端口是备份端口:也称之为 Slave 端口。 -Monitor Link 组也称之为“监控链路组&#xff0c;由上行端口和下行端口共同组成。下行…