【Go 快速入门】泛型 | 类型约束 | 泛型接口 | 通用数据结构

文章目录

    • 泛型
      • 类型参数
      • 类型实例化
      • 类型约束
      • 类型集
        • 并集 & 交集
      • 泛型接收者
      • 泛型方法
      • 泛型接口
        • 两种接口类型
        • 泛型接口
    • 泛型实现通用数据结构
      • 数组链表
      • 数组队列

本节项目地址:06-GenericsQueue

泛型

如果你经常要分别为不同的类型写完全相同逻辑的代码,那么使用泛型将是最合适的选择

类型参数

类型形参列表:T int | float64

func min[T int | float64](a, b T) T {if a <= b {return a}return b
}func function01() {x := min[int](10, 20)y := min[float64](0.1, -0.2)fmt.Println(x, y) // 10 -0.2
}

类型形参:T,类型实参:int

类型实例化

min 函数提供类型参数(在本例中为 intfloat64 )称为实例化。

类型实例化:

  • 编译器在整个泛型函数或类型中,将所有类型形参替换为它们各自的类型实参。
  • 编译器验证每个类型参数是否满足相应的约束。
fmin := min[float64] // 类型实例化
z := fmin(1.1, 2.2)
fmt.Println(z) // 1.1

类型约束

Go支持将类型约束单独拿出来定义到接口中,从而让代码更容易维护。

type Int interface {int | int8 | int16 | int32 | int64
}
type Uint interface {uint | uint8 | uint16 | uint32
}
type Float interface {float32 | float64
}
type Slice[T Int | Uint | Float] []T  // 使用 '|' 将多个接口类型组合

两种常见的类型约束:

类型约束字面量:通常 interface{} 可省略

func max[T interface{int | float64}](a, b T)  T {if a >= b {return a}return b
}

约束类型:事先定义,并支持复用

type Value interface {int | float64
}func max1[T Value](a, b T) T {if a >= b {return a}return b
}

几种语法错误

  1. 定义泛型类型,基础类型不能只有类型形参:
// 类型形参不能单独使用
type Type[T int | float32] T
  1. 类型约束语义被编译器误认为是表达式:
// 误认为定义一个存放切片的数组,长度 T * int
type NewType [T * int][]T// | 误以为是按位或
type NewType[T *int | *float32] []T

解决办法:可使用 interface{} 包裹或加上逗号:

type NewType[T interface{*int}] []T
type NewType[T interface{*int | *float64}] []T// 类型约束只有一个类型,可以添加逗号消除歧义
type NewType[T *int,] []T

类型集

从 Go 1.18 开始,一个接口不仅可以嵌入其他接口,还可以嵌入任何类型、类型的联合或共享相同底层类型的无限类型集合。

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

  • | 符号

T1 | T2表示类型约束为 T1T2 这两个类型的并集

  • ~ 符号

~T 表示所有底层类型是 T 的类型集合,~ 后面只能是基本类型

type Map[K int | string, V float32 | float64] map[K]V
type IValue interface {~string | ~int
}
type MyInt intfunc sum[T IValue](a []T) (res T) {for _, e := range a {res += e}return
}func function04() {m := Map[int, float64]{}m[1] = 1.0m[2] = 2.0fmt.Println(m) // map[1:1 2:2]a := []MyInt{1, 2, 3}fmt.Println(sum(a)) // 6
}
并集 & 交集
// 类型并集
type Int interface {~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
type Uint interface {~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
// 类型交集
type A interface {IntUint~string
}
  1. 类型取并集时,用 | 连接多个类型,这些类型必须不交集,相交类型是接口不受约束
// 错误,overlapping terms ~int and int
type B interface {int | ~int
}// 正确
type B interface {int | interface{ ~int }
}
  1. 并集中不能有类型实参
// 错误
type C[T int | string] interface {~float64 | T
}
  1. 接口不能直接或间接并入自己
// 错误,D嵌入E,间接并入了自己
type D interface {E
}type E interface {D
}
// 错误
type F interface {int | ~float64 | F
}
  1. 带方法的接口,不能写入接口的并集中
type ReadWriter[T any] interface {Read(data T) (newData T)Writer(data T) error
}
// 错误
type G interface {int | ReadWriter[int]
}

泛型接收者

定义一个新类型后,可以给新类型添加方法,那么同样也可以给泛型类型添加方法,如下:

  • 本例子为泛型类型 MyFloat[T] 定义了类型方法 avg,计算数组平均值
  • 在使用泛型类型前,始终需要先对类型实参实例化
type MyFloat[T ~float32 | ~float64] []Tfunc (mf MyFloat[T]) avg() T {var res Tfor _, e := range mf {res += e}return res / T(len(mf))
}func function05() {var fa MyFloat[float32] = []float32{1.0, 2.1, 3.2}fmt.Println(fa.avg()) // 2.1000001f64 := MyFloat[float64]{1.0, 2.1, 3.2}fmt.Println(f64.avg()) // 2.1
}

泛型方法

Go 目前不支持泛型方法,如下:

type MyStruct struct {}
// 错误:Method cannot have type parameters
func (ms MyStruct) Add[T int | string](other T) MyStruct {}

在方法中使用泛型,可以通过接收者来使用,如下:

type MyStruct[T int | string] struct {value T
}func (ms MyStruct[T]) Add(other T) MyStruct[T] {ms.value += otherreturn ms
}func function06() {a := MyStruct[int]{1}b := MyStruct[string]{"1"}fmt.Println(a.Add(2), b.Add("34")) // {3} {134}
}

泛型接口

两种接口类型
  1. 基本接口:接口中只有方法

  2. 一般接口:接口中不只有方法,还有类型

一般接口不能用来定义变量,只能用于泛型的类型约束中。

// 基本接口
type MyErr interface {Error() string
}// 一般接口
type MyUint interface {~uint | ~uint8 | ~uint16
}func function07() {var a MyErr = fmt.Errorf("这是基本接口")fmt.Println(a.Error()) // 这是基本接口// 错误:不能使用一般接口定义变量// var b MyUint
}
泛型接口

泛型接口,Processer[T] 在实例化后,即为基本接口,可以用于创建变量,如下 XML 类型实现了 Processer[string] 接口:

type Processer[T any] interface {Process(data T) TSave(data T) error
}type XML struct{}func (x XML) Process(data string) (newData string) {return ""
}
func (x XML) Save(data string) error {return errors.New("")
}func function08() {var a Processer[string] = XML{}a.Process("")a.Save("")
}

泛型接口,ProcesserNormal[T] 实例化后,即为一般接口,只能用于类型约束,不能用于定义变量。笔者没探索出来有啥用 O(∩_∩)O

type ProcesserNormal[T any] interface {int | ~struct{ value T }Process(data T) TSave(data T) error
}

其余需要注意的:

  1. 匿名结构体不支持泛型
type TypeStruct[T int | string] struct {Name stringData []T
}func function03() {x := TypeStruct[int]{"Cauchy", []int{1, 2, 3}}fmt.Println(x) // {Cauchy [1 2 3]}/* 匿名结构体不支持泛型y := struct[T []int|[]string]{Name stringData T}[int]{"AQ",[]int{1, 2, 3},}*/
}
  1. 当你需要针对不同类型书写同样的逻辑,使用泛型来简化代码是最好的 (比如你想写个队列,写个链表、栈、堆之类的数据结构)

泛型实现通用数据结构

通用数据结构实现参考:https://pkg.go.dev/github.com/emirpasic/gods/v2

数组链表

下述实现,提及 Go 内置的一个可比较对象类型 —— comparable接口。comparable 接口是由所有可比较类型实现的接口,只能用作类型参数约束,不能用作变量类型。

  1. 泛型容器接口 containers/containers.go,所有容器都需要实现其内部方法:
type Container[T any] interface {Empty() boolSize() intClear()Values() []TString() string
}
  1. 泛型链表接口 lists/lists.go,所有链表都要实现其内部方法:
type List[T comparable] interface {Get(index int) (T, bool)Remove(index int)Add(values ...T)Contains(values ...T) boolSort(comparator utils.Comparator[T])Insert(index int, values ...T)Set(index int, value T)containers.Container[T]
}
  1. 泛型可比较对象 utils/comparator.go
type Comparator[T any] func(x, y T) int
  1. 泛型数组链表 lists/arraylist/arraylist.go
package arraylistimport ("06-GenericsTest/lists""06-GenericsTest/utils""fmt""slices""strings"
)// 断言 List 实现接口 lists.List
var _ lists.List[int] = (*List[int])(nil)// List 链表结构
type List[T comparable] struct {elements []Tsize     int
}const (growthFactor = float32(2.0)  // 扩容因子shrinkFactor = float32(0.25) // 缩容因子
)// New 创建链表
func New[T comparable](values ...T) *List[T] {list := &List[T]{}if len(values) > 0 {list.Add(values...)}return list
}// Add 向链表末尾添加元素
func (l *List[T]) Add(values ...T) {l.growBy(len(values))for _, value := range values {l.elements[l.size] = valuel.size++}
}// Get 获取下标 index 的元素
func (l *List[T]) Get(index int) (T, bool) {if !l.withinRange(index) {var t Treturn t, false}return l.elements[index], true
}// Remove 移除下标 index 的元素
func (l *List[T]) Remove(index int) {if !l.withinRange(index) {return}clear(l.elements[index : index+1])copy(l.elements[index:], l.elements[index+1:l.size])l.size--l.shrink()
}// Contains 判断是否包含元素
func (l *List[T]) Contains(values ...T) bool {for _, searchValue := range values {found := falsefor i := 0; i < l.size; i++ {if l.elements[i] == searchValue {found = truebreak}}if !found {return false}}return true
}// Values 获取链表元素切片
func (l *List[T]) Values() []T {newElements := make([]T, l.size, l.size)copy(newElements, l.elements[:l.size])return newElements
}// IndexOf 获取元素所在下标
func (l *List[T]) IndexOf(value T) int {if l.size == 0 {return -1}for index, element := range l.elements {if element == value {return index}}return -1
}// Empty 判断链表为空
func (l *List[T]) Empty() bool {return l.size == 0
}// Size 获取元素个数
func (l *List[T]) Size() int {return l.size
}// Clear 清空链表
func (l *List[T]) Clear() {l.size = 0l.elements = []T{}
}// Sort 链表元素排序
func (l List[T]) Sort(comparator utils.Comparator[T]) {if len(l.elements) < 2 {return}slices.SortFunc(l.elements[:l.size], comparator)
}// Swap 交换两个元素
func (l *List[T]) Swap(i, j int) {if l.withinRange(i) && l.withinRange(j) {l.elements[i], l.elements[j] = l.elements[j], l.elements[i]}
}// Insert 指定位置插入
func (l *List[T]) Insert(index int, values ...T) {if !l.withinRange(index) {if index == l.size {l.Add(values...)}return}length := len(values)l.growBy(length)l.size += lengthcopy(l.elements[index+length:], l.elements[index:l.size-length])copy(l.elements[index:], values)
}// Set 指定位置设置元素
func (l *List[T]) Set(index int, value T) {if !l.withinRange(index) {if index == l.size {l.Add(value)}return}l.elements[index] = value
}// String 字符串显示链表
func (l *List[T]) String() string {str := "ArrayList: "values := make([]string, 0, l.size)for _, value := range l.elements[:l.size] {values = append(values, fmt.Sprintf("%v", value))}str += "[" + strings.Join(values, ", ") + "]"return str
}// withinRange 判断是否越界
func (l *List[T]) withinRange(index int) bool {return index >= 0 && index < l.size
}// resize 链表底层切片重新分配地址
func (l *List[T]) resize(cap int) {newElements := make([]T, cap)copy(newElements, l.elements)l.elements = newElements
}// growBy 判断是否增加 n 各元素需要扩容
func (l *List[T]) growBy(n int) {currentCapacity := cap(l.elements)if l.size+n >= currentCapacity {newCapacity := int(growthFactor * float32(currentCapacity+n))l.resize(newCapacity)}
}// shrink 缩容
func (l *List[T]) shrink() {if shrinkFactor == 0.0 {return}currentCapacity := cap(l.elements)if l.size <= int(float32(currentCapacity)*shrinkFactor) {l.resize(l.size)}
}

数组队列

  1. 泛型队列接口 queues/queues.go
// Queue 泛型队列接口
type Queue[T comparable] interface {Enqueue(value T)Dequeue() (value T, ok bool)Peek() (value T, ok bool)containers.Container[T]
}
  1. 泛型数组队列 queues/arrayqueue/arrayqueue.go
package arrayqueueimport ("06-GenericsTest/lists/arraylist""06-GenericsTest/queues""fmt""strings"
)var _ queues.Queue[int] = (*Queue[int])(nil)// Queue 队列结构
type Queue[T comparable] struct {list *arraylist.List[T]
}// New 创建队列
func New[T comparable]() *Queue[T] {return &Queue[T]{list: arraylist.New[T]()}
}// Enqueue 入队
func (q *Queue[T]) Enqueue(value T) {q.list.Add(value)
}// Dequeue 出队
func (q *Queue[T]) Dequeue() (value T, ok bool) {value, ok = q.list.Get(0)if ok {q.list.Remove(0)}return
}// Peek 队头元素
func (q *Queue[T]) Peek() (value T, ok bool) {return q.list.Get(0)
}// Empty 判断队列为空
func (q *Queue[T]) Empty() bool {return q.list.Empty()
}// Size 获取队列元素个数
func (q *Queue[T]) Size() int {return q.list.Size()
}// Clear 清空队列
func (q *Queue[T]) Clear() {q.list.Clear()
}// Values 获取队列元素切片
func (q *Queue[T]) Values() []T {return q.list.Values()
}// String 字符串显示队列
func (q *Queue[T]) String() string {str := "ArrayQueue: "values := []string{}for _, value := range q.list.Values() {values = append(values, fmt.Sprintf("%v", value))}str += "[" + strings.Join(values, ", ") + "]"return str
}

参考:https://blog.csdn.net/u014374975/article/details/133905842

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

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

相关文章

输了,腾讯golang一面凉了

本月正值4月,是金三银四的找工作的最佳时机。同时竞争也是很大,因为每年这个时候快要毕业的大学生也进去了找工作的潮水中。 今天分享我的一位大佬朋友CC,勇闯腾讯golang的面试经历。 这次面试问题的方向主要集中在计算机基础个网络方面。 下面是主要问到的问题。 第一个…

Word文档如何更改页面背景颜色?

在Microsoft Word中&#xff0c;设置页面颜色的方法有多种&#xff0c;以下为其中几种常用的方法&#xff1a;&#xff08;为office2016版本操作&#xff09; 方法一&#xff1a;使用主题颜色 1. 打开Word文档&#xff0c;在菜单栏中选择“设计”。 2. 在“设计”选项卡中&a…

泰山众筹:低门槛高回报的电商营销新模式

大家好&#xff0c;我是吴军&#xff0c;来自一家专注于软件开发的公司&#xff0c;担任产品经理一职。今天&#xff0c;我想与大家分享一种备受瞩目的商业模式——泰山众筹。 泰山众筹之所以能够在市场上迅速走红&#xff0c;其背后的原因值得我们深入探讨&#xff1a; 首先&…

【神经网络与深度学习】文本情感分类

数据准备 AclImdb – v1 Dataset 是用于二进制情绪分类的大型电影评论数据集&#xff0c;其涵盖比基准数据集更多的数据&#xff0c;其中有 25,000 条电影评论用于训练&#xff0c;25,000 条用于测试&#xff0c;还有其他未经标记的数据可供使用。 数据预处理和数据装载 imp…

idm线程越多越好吗 idm线程数多少合适

IDM&#xff08;Internet Download Manager&#xff09;是一款流行的下载管理软件&#xff0c;它支持多线程下载&#xff0c;这意味着它可以同时建立多个连接来下载文件的不同部分&#xff0c;从而提高下载速度。我们在使用IDM的时候总是有很多疑问&#xff0c;今天我们学习IDM…

游戏开发者必看:Perforce Helix Core 的功能特点及游戏开发中的常用工具、典型用例介绍

「不出海&#xff0c;即出局」随着全球化的加速发展&#xff0c;企业出海已成燎原之势。日前&#xff0c;2024 亚马逊云科技出海全球化论坛在深圳成功举办。龙智携手 Perforce 亮相游戏行业展区&#xff0c;展示了Perforce Helix Core如何与主流游戏开发引擎高效集成&#xff0…

Pytest精通指南(12)Parametrize源码拆解

文章目录 前言Parametrize 参数化Parametrize 源码分析Parametrize 使用说明一个参数的参数化多个参数的参数化验证类中有多个测试函数验证变量或函数传递参数化验证笛卡尔积拓展用法 前言 在 pytest 中&#xff0c;有两种常见的参数化方法&#xff1a; pytest.mark.parametriz…

哈希密码破解方法汇总

案例: 如何破译 254aa248acb47dd654ca3ea53f48c2c26 e93a1ec56258df7674c4 258df7674c4 该hash加密串的原文信息 步骤: 1)通过Hash Analyzer - TunnelsUP站点了解该hash加密串所使用的哈希加密算法类型。 可知,使用了 sha2-256 加密算法。 2) 访问example_hashes [hash…

C语言-Linux:简单实现Linux的cp指令

在Linux操作系统中&#xff0c;cp 命令是一种常用的文件和目录复制工具。以下是对cp命令的详细说明&#xff0c;包括其基本语法、常用选项以及一些示例用法&#xff1a; 基本语法&#xff1a; cp [选项] 源 目标 其中&#xff1a; 源&#xff1a;指要复制的文件或目录。目标&am…

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之十一 简单给视频添加水印图片效果

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之十一 简单给视频添加水印图片效果 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之十一 简单给视频添加水印图片效果 一、简单介绍 二、简单给视频添加水印图片效果实现…

【Linux学习】初识Linux指令(二)

文章标题 1.rm 指令2.man指令3.nano指令4.cp指令5.mv指令6.alias指令7. cat与8.echo指令 ⚶文章简介 ⚶本篇文章继上篇文章Linux指令讲解&#xff0c;本篇文章主要会涉及到的指令会有&#xff1a;rm指令与 *&#xff08;通配符&#xff09;的搭配使用&#xff0c;man指令&…

[重学Python]Day3 函数和模块的使用

[重学Python]Day3 函数和模块的使用 一、函数的作用二、定义函数三、函数的参数四、用模块管理函数五、练习&#xff08;一&#xff09;实现计算最大公约数和最小公倍数的函数&#xff08;二&#xff09;、实现判断一个数是不是回文数的函数&#xff08;三&#xff09;、实现判…

专业SEO优化指南:设置网站关键词的详细步骤

在网站SEO优化的过程中&#xff0c;关键词的设置是提升网站排名的关键步骤之一。那么&#xff0c;作为一名专业的SEO人员&#xff0c;如何有效地进行关键词设置呢&#xff1f;以下是一些详细的步骤&#xff1a; 1. 确定网站的核心关键词。 这需要深入理解网站的主题或产品。通…

整体性学习

整体性学习的顺序&#xff1a; 1.获取 2.理解&#xff08;明白&#xff09;3.拓展&#xff08;探究&#xff09;4.纠错&#xff08;调试&#xff09;5.应用 测试伴随每一个过程 例如&#xff1a; 吃饭&#xff08;去学习&#xff09;–>点菜&#xff08;学什么&#xff0c…

实时数据同步之Maxwell和Canal

文章目录 一、概述1、实时同步工具概述1.1 Maxwell 概述1.2 Canal概述 2、数据同步工作原理2.1 MySQL 主从复制过程2.2 两种工具工作原理 3、MySQL 的 binlog详解3.1 什么是 binlog3.2 binlog 的开启3.3 binlog 的分类设置 4、Maxwell和Canal对比5、环境安装 二、Maxwell 使用1…

日本极致产品力|一个战略符号打造年销售超4亿份的冰淇淋大单品

日本赤城乳业有一款冰棍——ガリガリ君(GariGarikun)&#xff0c;凭借着自己的“纯粹”打入市场&#xff0c;几十年来它成为许多日本人的夏日必备。他让人记忆最深刻的是战略符号——ガリガリ君&#xff0c;让赤城乳业打造出年销售4亿份的冰淇淋大单品。它是如何做到的呢? 石油…

Django——CBV源码解析

Django——CBV源码解析 以下是views模块调用as_view()方法的代码示例 # urls.py from django.contrib import admin from django.urls import path import app.viewsurlpatterns [path(admin/, admin.site.urls),path(app/, app.views.task.as_view()), ]# views.py class t…

Day55 动态规划 part15

Day55 动态规划 part15 392.判断子序列 我的思路&#xff1a; 自己还是只能想到双指针法 解答: class Solution {public boolean isSubsequence(String s, String t) {if(s.length() 0) {return true;}if(s.length() > t.length() || t.length() 0) {return false;}ch…

性能再升级!UNet+注意力机制,新SOTA分割准确率高达99%

UNet结合注意力机制能够有效提升图像分割任务的性能。 具体来说&#xff0c;通过将注意力模块集成到UNet的架构中&#xff0c;动态地重新分配网络的焦点&#xff0c;让其更集中在图像中对于分割任务关键的部分。这样UNet可以更有效地利用其跳跃连接特性&#xff0c;以精细的局…

VMware安装Linux虚拟机(rocky9)

软件准备&#xff1a; VMware虚拟机ISO系统镜像文件 选择创建虚拟机→典型→下一步→点击稍后安装操作系统 选择Linux系统和对应版本 输入虚拟机名称和选择保存位置 设置磁盘大小 根据需要自定义硬件配置→完成 然后点击编辑虚拟机设置→CD/DVD→选择ISO镜像 然后开启虚拟机→…