Go-知识泛型

embedded/2024/10/18 15:41:42/

Go-知识泛型

  • 1. 认识泛型
  • 2. 泛型的特点
  • 3. 类型约束
    • 3.1 类型集合
    • 3.2 interface 类型集合
      • 3.2.1 内置interface类型集合
      • 3.2.2 自定义interface类型集合
        • 3.2.2.1 任意类型元素
        • 3.2.2.2 近似类型元素
        • 3.2.2.3 联合类型元素
      • 3.2.3 interface类型集合运算
      • 3.2.4 基于操作的类型集合
  • 4. 小例子
    • 4.1 map.Keys 获取map的全部key
    • 4.2 Set
    • 4.3 排序 Sort
  • 5. 总结

泛型是程序设计语言的一种风格或范式,允许程序员在编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。
Java,C++等多种编程语言都支持泛型,Go语言从1.18版本起也开始支持泛型

1. 认识泛型

1.1 不使用泛型

实现两个函数对map的value值进行累加,一个是int64类型的值,一个是float64类型的值

func SumInt64(m map[string]int64) int64 {var sum int64for _, v := range m {sum += v}return sum
}func SumFloat64(m map[string]float64) float64 {var sum float64for _, v := range m {sum += v}return sum
}

接着使用两个函数

func TestSum(t *testing.T) {ints := map[string]int64{"one":   234,"two":   6755,"three": 78675,}floats := map[string]float64{"one":   123.456,"two":   7865.9658,"three": 87906.865,}t.Logf("sum ints : %v , floats : %v", SumInt64(ints), SumFloat64(floats))
}

执行如下:
在这里插入图片描述

实现同样的功能,就因为处理的数据类型不一样,就需要为每种类型编写类似的重复代码。

1.2 使用泛型

泛型函数,就是吧函数的参数和返回值“泛化”,使逻辑通用。 通用并不是对所有类型都使用,所以在声明泛型函数时,需要声明适用的“参数类型”(类型约束).

func SumValue[K comparable, V int64 | float64](m map[K]V) V {var sum Vfor _, v := range m {sum += v}return sum
}

SumValue泛型函数通过[K comparable, V int64|fload64]声明了两个类型参数K,V,供函数参数和返回值使用。
类型参数K的类型必须为comparable类型,因为被用作map的key值,在Go语言中map的key值必须是可比较的类型。
类型参数V的类型可以是int64或float64,在声明时使用|组合支持的类型。
普通参数m 表示一个泛化的map,相应的返回值也是一个泛化的类型。
使用:

func TestSumValue(t *testing.T) {ints := map[string]int64{"one":   234,"two":   6755,"three": 78675,}floats := map[string]float64{"one":   123.456,"two":   7865.9658,"three": 87906.865,}t.Logf("sum ints : %v , floats : %v", SumValue(ints), SumValue[string, float64](floats))
}

执行结果
在这里插入图片描述

在调用泛型函数的地方,编译器会将泛型函数实例化,即使用真实的类型来替换类型参数,在调用时有两种方式:

  • 隐式调用: 调用泛型函数时使用缺省类型参数,让编译器根据实际的参数进行推导。(SumValue(ints))
  • 显示调用: 调用泛型函数时显式的指明类型参数。(SumValuestring, float64)

需要注意的是,编译器之所以能够推导出参数类型是因为函数存在入参,编译器通过传入的变量和函数的参数声明可以推导出参数类型,但对于没有函数参数的泛型函数来说,编译器
无法进行推导,也就无法实例化泛型函数,此时必须显式地调用并指定类型参数。
比如:
在这里插入图片描述

就无法推导出来返回值的类型了
而且显式调用,必须全部指定,不能指定部分类型
在这里插入图片描述

2. 泛型的特点

泛型的英文表述为generic,即一般化,泛化,具体来讲就是函数和类型的泛化。在泛型被引入之前一个函数所能接收的参数类型及
所能处理的数据类型是确定的,但泛型函数却能接受和处理多种类型,对于类型也是同样的道理。
泛型主要包含三方面的内容:

  1. 函数的泛化
  2. 类型的泛化
  3. 接口的扩展

2.1 函数泛化

为了支持泛型,Go语言函数扩展为可以接受一个使用方括弧表示的类型参数;
func SumValue1[K comparable, V int64 | float64, T int64 | float64](m map[K]V) T
同普通的函数参数类似,类型参数中的每个参数也有一个类型,比如参数K,V的类型分别为 comparable(伴随泛型而引入的内置interface类型,表示可比类型)
和int64|float64(表示int64或float64)。
函数中的类型参数是可选的,没有类型参数的函数是传统的函数,带有类型参数的函数则是泛型函数。
即便Go 1.18 引入了泛型并且扩展了函数,但仍然保持兼容(在这里小小的谴责一下 python 和 scala )。
类型参数中的类型正式的名称是类型约束,用来约束类型的范围。 上面额函数可以接受多种类型的map为参数,考虑到所有的map的key的类型都是comparable类型,
那么只要一个map的值类型是int64或float64就能调用泛型函数。
比如: map[int]int64,map[int64]int64,map[float64]float64,...

2.2 类型泛化

泛型同样扩展了类型的表示方法,允许在创建自定义类型时也能接受一个使用方括弧表示的类型参数。
type arr[T int|int64] []T
声明arr类型,可以容纳int或者int64的切片。
这种声明中带有类型参数的类型被称为泛型类型。
泛型类型必须通过类型参数实例化后才可以使用。
var arrInt arr[int]
var arrInt64 arr[int64]
但是当实例化允许范围之外的类型时,会编译异常
在这里插入图片描述

在实例化一个泛型类型时,必须指定类型参数(编译器无法自动推导)

泛型类型同普通类型一样,同样允许定义方法,但是其类型必须带上类型参数:

func (a *arr[T]) add(x T) {*a = append(*a, x)
}
func TestArrAdd(t *testing.T) {var a arr[int]a.add(3)a.add(4)t.Logf("res : %v", a)
}

执行结果
在这里插入图片描述

泛型类型定义方法时,必须指定类型参数,但参数名可以与泛型类型声明不同。

func (a *arr[X]) add1(x X) {*a = append(*a, x)
}

定义时使用T,但是在使用的时候,可以与声明时的名字不同。
如果方法体中并未使用类型参数,甚至可以使用_省略

func (a *arr[_]) add2(x _) {*a = append(*a, x)
}

但是不管是换个名字还是使用_并没有任何好处,反而降低了可读性。

3. 类型约束

无论函数和类型如何泛化,都需要类型参数来限定其泛化的范围,类型参数使用类型的集合表示范围。

3.1 类型集合

func SumValue1[K comparable, V int64 | float64, T int64 | float64](m map[K]V) T
该函数的类型参数中K的类型限定为comparable,V 的类型限定为int64或float64。
comparable是interface类型,int64|float64是组合类型,都代表一个类型集合,用于约束泛化的范围。
使用|来组合多个类型,从而形成一个类型集合。

3.2 interface 类型集合

泛型特性被引入之前,interface仅表示一个方法集合,实现了该方法结合的所有类型都可认为实现了这个interface。
泛型的设计中,对interface进行了扩展,interface将不在仅仅表示方法集合,它还可用于表示类型集合,同理,集合内所有类型都可认为实现了这个interface。

3.2.1 内置interface类型集合

comparable就是跟随泛型被引入的内置interface类型
在这里插入图片描述

除了comparable还有any。
comparable表示可比较类型的集合,仅能用于类型参数中。
any不仅在类型参数中表示任意类型的集合,还可以在非泛型场景中作为interface{}的别名使用。

3.2.2 自定义interface类型集合

除了内置的comparable和any两种类型可作为类型约束使用,用户还可以使用interface来定义类型集合。
泛型之前,interface类型中仅允许包含方法或内嵌interface两种元素,引入泛型后,interface类型将允许使用另外三种元素以表示一个类型集合:

  1. 任意类型元素(如 int)
  2. 近似类型元素(使用表示法,如int)
  3. 联合类型元素(使用|表示法,如int|int64)

需要注意的是,如果interface类型中使用了这三种元素的任意一种,那么这个interface只能用于泛型的类型参数

3.2.2.1 任意类型元素

任意类型(包含interface类型)都可以出现在一个新的interface类型中,用于表示一个仅用于类型参数的集合

type Mint interface {int
}func addMint[M Mint](m1, m2 M) M {return m1 + m2
}
func TestMint(t *testing.T) {t.Log(addMint(3, 4))
}

在这里插入图片描述

此时该interface表示的数据集仅包含一种类型,且仅能用于泛型场景中的类型参数中。
interface类型和自定义类型都可以出现在interface中从而表示一个类型集合。
可以定义新的泛型interface,不能使用泛型interface定义interface 方法集合
interface泛型用于interface
在这里插入图片描述

但是不能将interface泛型用于interface方法集合
在这里插入图片描述

但是如果显式的声明了泛型,那么就可以使用
在这里插入图片描述

并且该interface的方法集合也能像之前一样实现
在这里插入图片描述

因为在定义interface泛型的时候,限定是int,所以只有int类型才算是实现了方法
在这里插入图片描述

这样来看,使用interface泛型类型,可以限定什么样的方法算是实现。

如果将float64加入到interface泛型中,那么float64的方法也算是实现
在这里插入图片描述

3.2.2.2 近似类型元素

在使用 interface声明类型集合时,可以使用~<type>来制定一组类型,只要其底层类型为同一类型即包含在这个集合中。
因为在Go中,可以通过type取别名,而泛型又时通用这个含义。
比如创建一个string的泛型函数,但是因为使用了type对string取了别名,结果别名类型就无法使用泛型函数。
近似类型元素就是可以让type取了别名的类型也能使用
不使用近似类型
在这里插入图片描述

使用近似类型
在这里插入图片描述

只要底层类型相同,就能使用泛型函数

需要注意的是,~之后的类型必须是某个底层类型,换句话说,一个类型的底层类型不是自身就不能使用~
在这里插入图片描述

另外,interface 自身也不能用于定义近似类型集合,因为interface的底层类型并不确定。

3.2.2.3 联合类型元素

前面使用~定义的元素集合仅能包括一组底层类型一致的类型,又是可能需要联合多种类型,甚至联合多种底层类型一致的类型,此时可以用
|定义一个更宽泛的类型集合

type MInteger interface {int | int8 | int16 | int32 | int64
}

但是上述定义仅能支持底层类型,不支持别名
更进一步,可以把所有底层类型也包含进来

type MAnyInteger interface {~int | ~int8 | ~int16 | ~int32 | ~int64
}

这样即使是别名类型,也能支持。

3.2.3 interface类型集合运算

前面使用interface声明类型集合时,均使用一行代码制定一个集合(一个子集),事实上interface支持按行制定多个自己和,这些自己和取交集形成最终的集合

type NewString interface {~stringstring
}

NewString的类型集合由两个子集组成,一个是所有底层类型为string的集合,另一个是string单一类型,两个子集取交集,最终的类型集合将只包含string单一类型
在这里插入图片描述

3.2.4 基于操作的类型集合

假设顶一个泛型函数来比较元素大小

type Ordered interface {~int|~int8|~int16|~int32|~int64|~uint|~uint8|~uint16|~uint32|~uint64|~float32|~float64|~string
}func Equals[T Ordered](a, b T) bool {if a == b {return true}return false
}

定义的Ordered泛型类型是全部可以使用==比较的底层类型,并且包含别名类型,泛型函数限定了Ordered泛型类型。
copmarable和Ordered类似,范围更大。

4. 小例子

4.1 map.Keys 获取map的全部key

将map中的所有key取出来,然后存入切片中返回

func Keys[K comparable, V any](m map[K]V) []K {res := make([]K, 0, len(m))for k, _ := range m {res = append(res, k)}return res
}

类型参数K被用于声明了一个泛型的切片,然后把遍历到的key添加到切片中并返回。
任意的map都能使用Keys泛型函数

func TestKeys(t *testing.T) {t.Log(Keys(map[string]struct{}{"one":   {},"tow":   {},"three": {},}))t.Log(Keys(map[int]int{1: 1,2: 2,3: 3,}))
}

在这里插入图片描述

4.2 Set

Set可以存储一组不重复的数据,广泛用于需要去重的场景。很多编程语言比如Java,C++都提供了相应的实现,但是在Go语言中并没有Set类型。
有了泛型可以自己实现了

// 定义 Set
type Set[T comparable] map[T]struct{}// 创建 Set
func MakeSet[T comparable]() Set[T] {return make(Set[T])
}// 添加元素
func (s Set[T]) Add(k T) {s[k] = struct{}{}
}// 删除元素
func (s Set[T]) Delete(k T) {delete(s, k)
}// 判断是否包含
func (s Set[T]) Contains(k T) bool {_, ok := s[k]return ok
}// 返回长度
func (s Set[T]) Len() int {return len(s)
}// 遍历
func (s Set[T]) Iterate(f func(T)) {for k := range s {f(k)}
}func TestSet(t *testing.T) {set := MakeSet[int]()set.Add(1)set.Add(2)set.Add(1)set.Add(3)t.Log(set.Len()) // 预期 3t.Log(set.Contains(2)) // 预期 trueset.Delete(1) t.Log(set.Len()) // 预期 2t.Log(set.Contains(1)) // 预期 falsesum := 0set.Iterate(func(i int) {sum += i})t.Log(sum) // 预期 5
}

在这里插入图片描述

使用泛型实现的Set可适用于任意的可比较类型,行为与其他语言实现的Set基本类似,但是只能函数调用,不能使用下标操作访问元素。
上面实现的Set底层使用一个map实现,并不是线程安全的,还可以进一步使用自定义扩展

type SyncSet[C comparable] struct {l sync.RWMutexm map[C]struct{}
}

在读操作的时候,加读锁,在写操作的时候加写锁。

4.3 排序 Sort

要对切片中的元素进行排序,在标准库提供sort.Slice之前,每种类型的切片都需要实现sort.Interface接口中的三个方法
在这里插入图片描述

然后使用sort.Sort方法进行排序,即便后来标准库中引入了sort.Slice,但是使用时仍然需要提供一个排序函数。
使用泛型实现一个针对切片的通用排序函数

type Ordered interface {~int | ~int8 | ~int16 | ~int32 | ~int64 |~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |~float32 | ~float64 |~string
}type orderSlice[O Ordered] []Ofunc (o orderSlice[O]) Len() int {return len(o)
}
func (o orderSlice[O]) Less(i, j int) bool {return o[i] < o[j]
}
func (o orderSlice[O]) Swap(i, j int) {o[i], o[j] = o[j], o[i]
}
func OrderSlice[O Ordered](s []O) {// s 被转换为 []Ordered 类型,也就是 orderSlice,然后排序// 因为 orderSlice已经实现了排序的接口,不需要额外实现了sort.Sort(orderSlice[O](s))
}
func TestOrder(t *testing.T) {is := []int{3, 4, 5, 1, 2}OrderSlice(is)t.Log(is)ss := []string{"he", "ww", "ss"}OrderSlice(ss)t.Log(ss)
}

在这里插入图片描述

使用泛型实现排序幻术也有一定的局限性,因为不容易处理复杂的符合类型,比如自定义的struct类型。

5. 总结

反形式衡量编程语言技术完备度的一个重要参考指标,但是也是一个比较争议的技术。
泛型的缺失导致开发者不得不编写重复的代码,或者编写相对通用但缺少类型安全的代码,甚至有些项目使用代码自动生成技术来摆脱编写
重复代码的烦恼,从这方面来看,Go确实需要泛型
但是引入泛型也是有一定成本的,比如泛型的三个困局:

  • 没有泛型(C语言)会降低程序员的生产力,但不会增加语言的复杂度
  • 泛型会增加编译器的负担(C++),可能会编译出很多冗余的代码,进而拖慢编译时间
  • 泛型会降低运行时的性能(Java),避免编译大量冗余代码的后果是增加运行时的开销

Go语言早在1.17版本时就推出了试用版本,但在1.18中还是用了极大的篇幅说明泛型的种种风险。
https://golang>golang.google.cn/doc/go1.18#generics


http://www.ppmy.cn/embedded/128490.html

相关文章

c++算法第3天

本篇文章包含三道算法题&#xff0c;难度由浅入深&#xff0c;适合新手练习哟 目录 第一题 题目链接 题目解析 代码原理 代码编写 本题总结 第二题 题目链接 题目解析 代码原理 代码编写 第三题 题目链接 题目解析 代码原理 代码编写 第一题 题目链接 [NOIP2…

惊喜!又一本开源免费的大模型书来了(附PDF)

介绍 《自然语言处理&#xff1a;大模型理论实践》&#xff08;预览版&#xff09;一书以自然语言处理中语言模型为主线&#xff0c; 涵盖了从基础理论到高级应用的全方位内容&#xff0c;逐步引导读者从基础的自然语言处理技术走向大模型的深度学习与实际应用。 自然语言处理…

Leetcode 1223 LCA of Deepest TreeNode

题意&#xff0c;找到所有最深的叶子节点的LCA https://leetcode.com/problems/lowest-common-ancestor-of-deepest-leaves/description/ 第一个想法是模块的想法, LCA 找到所有最深的叶子节点两两组合 可行&#xff0c;但是算法复杂度很高而且你先要从顶到下&#xff0c;再从…

2019年计算机网络408真题解析

第一题&#xff1a; 解析&#xff1a;OSI参考模型第5层完成的功能 首先&#xff0c;我们需要对OSI参考模型很熟悉&#xff1a;从下到上依次是&#xff1a;物理层-数据链路层-网络层- 运输层-会话层-表示层-应用层&#xff0c;由此可知&#xff0c;题目要问的是会话层的主要功能…

Facebook的全球影响力:跨文化交流与信息共享的前沿

引言 在数字化时代&#xff0c;社交媒体已成为全球沟通的重要平台。自2004年成立以来&#xff0c;Facebook迅速发展成为拥有超过20亿活跃用户的巨头。其强大的影响力使其成为跨文化交流与信息共享的前沿平台。 跨文化交流的促进 Facebook的多语言支持让来自不同文化背景的用户…

拉普拉斯:用“硬科技”解决行业核心痛点 先发优势突出获行业广泛认可

《金基研》森海/作者 杨起超 时风/编审 近年来&#xff0c;在全球低碳的产业政策引导和市场需求的双轮驱动下&#xff0c;光伏产业蓬勃发展&#xff0c;光伏电池片产量逐年上升。2022年至今&#xff0c;以TOPCon、XBC、HJT为代表的转换效率更高的新型高效电池片技术进入产业化进…

如何新建一个React Native的项目

要新建一个 React Native 项目&#xff0c;你可以使用 React Native 官方推荐的工具 React Native CLI 或者 Expo。两者的区别在于&#xff1a;React Native CLI 提供更多对原生代码的访问权限&#xff0c;适合构建复杂的应用&#xff1b;而 Expo 是一个开发工具链&#xff0c;…

关于(鉴定)专业性事实的审查

《民事诉讼法》第79条到82条、《民诉法司法解释》第121至123条、《证据规则》第79至84条&#xff0c;都是关于司法鉴定的规则&#xff0c;其核心含义有两层&#xff1a; 一是当事人可以对于专门性问题申请鉴定&#xff0c;但法院应当审查必要性与合理性&#xff0c;并要做好基…