事实上,泛型才是Go lang1.18最具特色的所在,但为什么我们一定要拖到后面才去探讨泛型?类比的话,我们可以想象一下给小学一年级的学生讲王勃的千古名篇《滕王阁序》,小学生有多大的概率可以理解作者的青云之志以及壮志难酬的愤懑心情?恐怕很难罢,是的,如果对Go lang的强类型语法没有一段时间的体验期,就很难理解泛型这种“反”静态语言概念。

基本概念

什么是泛型?泛型泛型,顾名思义,泛用的类型,说白了,就是在静态类型语言环境使用动态类型语言的特性:



package main  

import (
"fmt"
) func sum(a string, b string) string { s := a + b
return s
} func main() { a := "1"
b := "2" fmt.Println(sum(a, b))
}

比方说有一个函数可以实现两个字符串合并,参数声明了字符串,也就不支持其他的数据类型,但如果逻辑上差不多,需要两个整形求和的函数怎么办?那就得再写一个差不多的函数,这样就影响了代码逻辑的复用性。

相同逻辑下可以针对不同的数据类型进行泛用,这就是泛型的意义所在。

泛型声明

Go lang中的泛型使用 [] 来申明类型范围:

func sum[v int | float64 | string](a v, b v) v {  

	s := a + b  

	return s
}

如果是多个数据类型,可以使用|分隔,这里定义了一个泛型变量v,可以是整形、浮点以及字符串:

package main  

import (
"fmt"
) func sum[v int | float64 | string](a v, b v) v { s := a + b return s
} func main() { a := "1"
b := "2" fmt.Println(sum(a, b))
}

程序返回:

12

注意,由于参数的类型未定,所以返回值也必须是泛型类型,现在动态的把参数改为整形:

package main  

import (
"fmt"
) func sum[v int | float64 | string](a v, b v) v { s := a + b return s
} func main() { a := 1
b := 2 fmt.Println(sum(a, b))
}

返回值也因为参数类型的改变而改变:

3

藉此,我们就声明了一个可以“泛用”的函数。

高阶应用

事实上,泛型的出现并非可以丰富函数的声明和构建,更多的,是战略层面上的多样化选择,比如容器内的类型,进而言之,队列:

type Queue[T interface{}] struct {
elements []T
} // 将数据放入队列尾部
func (q *Queue[T]) Put(value T) {
q.elements = append(q.elements, value)
} // 从队列头部取出并从头部删除对应数据
func (q *Queue[T]) Pop() (T, bool) {
var value T
if len(q.elements) == 0 {
return value, true
} value = q.elements[0]
q.elements = q.elements[1:]
return value, len(q.elements) == 0
}

这里结构体的类型约束使用了空接口,代表的意思是所有类型都可以用来实例化泛型类型,同时基于泛型结构体,我们定义两个方法,分别是:入队和出队。

因为这个队列是泛型队列,所以队内元素的类型可以在实现结构体接口时进行定义:

package main  

import (
"fmt"
) type Queue[T interface{}] struct {
elements []T
} // 将数据放入队列尾部
func (q *Queue[T]) Put(value T) {
q.elements = append(q.elements, value)
} // 从队列头部取出并从头部删除对应数据
func (q *Queue[T]) Pop() (T, bool) {
var value T
if len(q.elements) == 0 {
return value, true
} value = q.elements[0]
q.elements = q.elements[1:]
return value, len(q.elements) == 0
} func main() { var q1 Queue[int] // 可存放int类型数据的队列
q1.Put(1)
q1.Put(2)
q1.Put(3)
fmt.Println(q1) var q2 Queue[string] // 可存放string类型数据的队列
q2.Put("A")
q2.Put("B")
q2.Put("C") fmt.Println(q2)
}

程序返回:

{[1 2 3]}
{[A B C]}

匿名函数和方法暂不支持泛型

Golang中,我们经常会使用匿名函数:

package main  

import (
"fmt"
) func main() { fn := func(a, b int) int {
return a + b
} // 定义了一个匿名函数并赋值给 fn fmt.Println(fn(1, 2)) // 输出: 3
}

程序返回:

3

大体上,和Python的lambda表达式类似,如果封装的逻辑相对简单或者和上下游逻辑连贯性较强,那么,在不影响代码可读性的前提下,我们就没必要单独声明一个函数,而是选择匿名函数。

但1.18版本中,匿名函数并不支持参数为泛型,因为匿名函数不能自己定义类型形参:

fnGeneric := func[T int | string](a, b T) T {
return a + b
}

程序报错:

./hello.go:9:19: syntax error: function literal must have no type parameters

但匿名函数可以使用已经被合法定义的泛型类型:

package main  

import (
"fmt"
) func test[T int | float32 | float64](a, b T) { // 匿名函数可使用已经定义好的类型形参
fn2 := func(i T, j T) T {
return i + j
} fmt.Println(fn2(a, b))
} func main() { test(1, 2) }

程序返回:

3

也就是说,匿名函数可以使用父级函数定义好的泛型类型参数,这意味着,在泛型函数内,我们可以通过匿名函数对逻辑进行二次封装。

同样地,1.18版本中的方法也不支持泛型:

type A struct {
} // 不支持泛型方法
func (receiver A) Add[T int | float32 | float64](a T, b T) T {
return a + b
}

程序报错:

syntax error: method must have no type parameters

但是和匿名函数类型,因为receiver支持泛型,所以我们可以声明结构体内receiver的参数为泛型类型:

package main  

import "fmt"  

type A[T int | float32 | float64] struct {
} // 方法可以使用类型定义中的形参 T
func (receiver A[T]) Add(a T, b T) T {
return a + b
} func main() { var a A[int]
res := a.Add(1, 2) fmt.Println(res) }

程序返回:

3

因为receiver声明了泛型参数,我们为结构体A绑定的方法也就可以直接使用声明好的泛型类型,和匿名函数直接用父级泛型是一个意思。

结语

事实上,静态语言在设计上基本都有泛型的概念,这并不是自我矛盾,对应的,在动态语言Python中为函数声明形参时,我们其实也可以指定具体的参数类型或者返回值类型,正所谓无招胜有招,真正的高手,可以脱离语言类型的桎梏,达到一种无我无众生的境界,比如,在固有思维模式中,降龙十八掌是一种至刚至猛的武功,威力无穷,无坚不摧,但郭大侠后期再使用这门神功时,降龙十八掌的劲力忽强忽弱,忽吞忽吐,从至刚之中竟生出至柔的妙用,那已是洪七公当年所领悟不到的境界,所以,刚柔并济、虚中有实、实中有虚、虚实相生才是泛型使用的最高境界。

因势而变,因时而动,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang泛型(generic)的使用EP15的更多相关文章

  1. 优动漫PAINT个人版绘制树叶教程

    超详细树叶绘制法,更有配套绘树小TIPE!让你画树So Easy~一秒变身,画树达人! 优动漫PAINT个人版软件下载:http://www.dongmansoft.com/chanpin.html ...

  2. M-移动端的webapp页面布局教程和webapp实战分析

    http://www.25xt.com/html5css3/8092.html 响应式设计 1 媒体查询 适用于不同固定宽度设计 媒体类型 : screen 屏幕 print 打印机 handheld ...

  3. 优动漫PAINT-牵牛花画法教程

    喇叭型对画者自身的塑形功力会有较高的要求,作者很靠谱的把他的塑形方式详细呈现了出来~ 对于这样的一个仿真效果的牵牛花完全可以使用优动漫PAINT完成,简单又快捷,软件下载:http://www.don ...

  4. 优动漫PAINT-绘制透明布料教程

    原是一篇日语教程,觉得挺不错的,就劳烦会日语的朋友帮忙翻译了,特此分享!希望可以帮助到大家在绘画上的学习!原教程转载优动漫官网. 作者:JaneMere 相关资讯还可以关注www.dongmansof ...

  5. 《VR入门系列教程》之3---运动追踪与输入设备

    运动追踪设备    第二种可以使人脑相信它真实处于虚拟世界的关键技术就是运动追踪技术,它可以通过追踪头部的运动状态实时更新渲染的场景.这与我们在真实世界中观看周围非常类似.    高速的惯性测量单元( ...

  6. 你有对象类,我有结构体,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang结构体(struct)的使用EP06

    再续前文,在面向对象层面,Python做到了超神:万物皆为对象,而Ruby,则干脆就是神:飞花摘叶皆可对象.二者都提供对象类操作以及继承的方式为面向对象张目,但Go lang显然有一些特立独行,因为它 ...

  7. JS特效@缓动框架封装及应用

    | 版权声明:本文为博主原创文章,未经博主允许不得转载. 一.变量CSS样式属性获取/赋值方法 给属性赋值:(既能获取又能赋值) 1)div.style.width 单个赋值:点语法,这个方法比较固定 ...

  8. c#的协变和逆变

    关于协变和逆变要从面向对象继承说起.继承关系是指子类和父类之间的关系:子类从父类继承,所以子类的实例也就是父类的实例.比如说Animal是父类,Dog是从Animal继承的子类:如果一个对象的类型是D ...

  9. .NET 4.0中的泛型逆变和协变

    转载自:http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html:自己加了一些理解 随Visual Studi ...

随机推荐

  1. QQ空间未授权评论_已忽略

    看群友们聊天时发现的, 大概是做了查看了动态访问时间的一个设置, 但是仅自己可见的说说还是被评论了的这么一个问题. 闲的没事就翻了一下找一下问题. 这个方法嘎嘎鸡肋, 可以说完全没用, 交到tsrc, ...

  2. JVM 输出 GC 日志导致 JVM 卡住,我 TM 人傻了

    本系列是 我TM人傻了 系列第七期[捂脸],往期精彩回顾: 升级到Spring 5.3.x之后,GC次数急剧增加,我TM人傻了:https://zhuanlan.zhihu.com/p/3970425 ...

  3. rhel7修改网卡名

    备份eno16777736网卡配置,并复制出一个ifcfg-eth0 [root@rhel7 network-scripts]# cp ifcfg-eno16777736 ifcfg-eno16777 ...

  4. 揭开Vue异步组件的神秘面纱

    简介 在大型应用里,有些组件可能一开始并不显示,只有在特定条件下才会渲染,那么这种情况下该组件的资源其实不需要一开始就加载,完全可以在需要的时候再去请求,这也可以减少页面首次加载的资源体积,要在Vue ...

  5. InheritableThreadLocal 在线程池中进行父子线程间消息传递出现消息丢失的解析

    在日常研发过程中,我们经常面临着需要在线程内,线程间进行消息传递,比如在修改一些开源组件源码的过程中,需要将外部参数透传到内部,如果进行方法参数重载,则涉及到的改动量过大,这样,我们可以依赖Threa ...

  6. 给妹子讲python-S01E01好用的列表

    1.python中的容器数据类型概述2.列表类型的异构性.有序性和本地可变性三大优势2.列表的基本操作(增.删.改.分片索引)3.列表的分片赋值与本地排序 [妹子说]今天开始学python啦,不过我们 ...

  7. maven编译 出现Process terminated

    问题: 解决方案: 在Settings中配置一下maven

  8. W10修改被改的默认打开文本方式

    今天不小心给默认方式打开点错了,身为处女座的我有洁癖非要非恢复过来,这是找到的 原文操作出处:win10系统如何还原文件默认打开方式?win10设置文件默认打开方式的方法 -Win7系统之家 (win ...

  9. TypeScript let与var的区别

    1.作用域不同 用var声明的变量,只有函数作用域和全局作用域,没有块级作用域.而let可以实现块级作用域,只能在代码块{}内有效,在{}之外不能访问,如下代码所示: { let a = 0; var ...

  10. pop!_OS换国内源

    今天给电脑换源了,虽然本来的源大部分好像也都连的上(不知道是不是错觉)换的这个:阿里云镜像开源站 先进入存放源的目录:` cd /etc/apt 里面有这些文件: sources.list是要修改的, ...