当前位置: 首页 > 科技观察

Go泛型:使用

时间:2023-03-13 05:19:25 科技观察

领先于Go泛型的基础知识泛型是Go语言多年来最激动人心和最根本的变化之一。没有泛型,很多人以此“鄙视”Go语言。当然,也有人觉得根本不需要泛型。有泛型,但这并不意味着您必须使用它们。平心而论,泛型在某些场景下仍然是必要的和有帮助的。现在已经确认Go1.18正式包含泛型(Go1.17已经可以试用了,但是默认不支持,见上一篇文章:骄傲:刚刚,Go已经默认支持泛型了)。然而,很多人仍然对泛型感到困惑。本文力求通俗易懂地解释泛型相关的内容。01什么是泛型Go是一门强类型语言,也就是说程序中的每一个变量和值都有特定的类型,比如int、string等,在函数签名中,我们需要指定参数和返回值的类型,如下:funcAdd(a,bint)int参数a和b的类型都是int,返回值类型也是int,结果是a和b的和。如果您现在需要一个对两个float64求和的函数怎么办?大概率会有这样一个函数:funcAddFloat(a,bfloat64)float64如果其他类型比较多(比如字符串加法),可能需要写更多对应版本的函数,很不方便和繁琐,一个一堆复制粘贴的代码。02如果Go中的泛型函数有泛型类型,如何解决上面的问题?只需要一个函数:funcAdd[Tany](a,bT)T是不是很简单?但是看着有点晕?稍微解释下:Add后面的[Tany],T表示类型标识,any表示T可以是a,b的任意类型,返回值的类型T和前面的T是同一类型。为什么要用[]而不是其他语言<>官方已经解释过了,可能是因为<>会产生歧义。本来打算用()但是最后用了[]因为太乱了。这意味着a、b和返回值可以是任何类型,但它们的类型是相同的。如何确定具体类型?根据调用时的实际参数确定。因此,我们现在可以这样使用:Add(1,2)Add(2.1,3.2)但是,此时代码会报错。你可以在本地使用Go1.17启用泛型进行实验,或者使用gotip版本,或者直接访问这个实验:https://go2goplay.golang.org/p/vTHnUA_8vOIpackagemainimport("fmt")funcAdd[Tany](a,bT)T{returna+b}funcmain(){fmt.Println(Add(1,2))fmt.Println(Add(2.1,3.2))}会报错:typecheckingfailedformmainprog.go2:8:9:invalidoperation:operator+notdefinedfora(variableoftypeparametertypeT)为什么?见下文。03Constraints显然,并不是所有的类型都支持加法运算。所以我们需要给出约束,指定可以添加的类型。在上面的代码中,我们对类型T使用了any,相当于没有约束。现在我们给出一个约束:typeAddableinterface{typeint,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64,uintptr,float32,float64,complex64,complex128,string}这是一个新的语法,叫做typelist(type列表)。首先,Addable重用了接口语法,即interface关键字,表示约束。具体约束的类型用类型指定,倍数用逗号分隔。现在Add函数中T的约束从any更改为Addable:funcAdd[TAddable](a,bT"TAddable")T{returna+b}现在再次起作用:https://go2goplay.golang.org/p/4J52QmGrc-M,发现正常。而且它还支持字符串、复数等:Add("polaris","xu")可以看到约束可以是任何接口类型。(any相当于一个空接口)还有一种场景:可比。例如,地图中的键需要具有可比性。例如,下面的代码:funcfindFunc[Tany](a[]T,vT"Tany")int{fori,e:=rangea{ife==v{returni}}return-1}T是任意类型,并且实际并非以上所有类型都具有可比性。该怎么办?当然,我们可以像上面的Addable一样定义一个约束,但是为了方便,Go提供了一个内置的comparable约束,也就是comparable的意思。参考如下代码:packagemainfuncfindFunc[Tcomparable](a[]T,vT"Tcomparable")int{fori,e:=rangea{ife==v{returni}}return-1}funcmain(){print(findFunc([]int{1,2,3,4,5,6},5))}04constraintspackage约束在写泛型代码的时候很常见。让我们看另一个从切片中找到最大值的例子:funcMax[Tany](input[]T"Tany")(maxT){for_,v:=rangeinput{ifv>max{max=v}}return}但运行会报错:fmt.Println(Max([]int{1,4,2,10}))//cannotcomparev>max(operator>notdefinedforT)这时候我们很自然地想到使用类似的方法上面添加函数自定义一个Constraints:Ordered,列出所有可能的类型。typeOrderedinterface{typeint,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64,uintptr,float32,float64,string}因为这样的需求比较普遍,为了这方面,官方提供了一个新的封装:constraints,一些约束是预定义的,详见https://github.com/golang/go/issues/45458。有了它,就不用再自定义Ordered约束了,而是要使用constraints包,即:funcMax[Tconstraints.Ordered](input[]T"Tconstraints.Ordered")(maxT)05上面的Generic类型,我们引入了Generic函数:函数可以接受任何类型。请注意,它不同于interface{}等任何类型。泛型类型中的类型不需要在函数内部做任何类型断言和反射工作,具体类型可以在编译时确定。我们知道Go是支持自定义类型的,比如标准库sort包中的IntSlice:typeIntSlice[]int此外还有StringSlice、Float64Slice等,一堆重复的代码。如果我们可以定义泛型类型,我们就不需要定义这么多不同的类型。比如:typeSlice[Tany][]T就可以理解了。使用时,对于int类型,是这样的:x:=Slice[int]{1,2,3}如果是作为函数参数,这样使用:funcPrintSlice[Tany](bSlice[T]"Tany")如果是这个类型定义方法是这样的:func(bSlice[T])Print()也就是说Slice[T]是作为一个整体存在的。当然,泛型也可以代替任何类型作为类型约束:typeSlice[Tcomparable][]T06小结通过本文的讲解,相信你对Go泛型有了基本的掌握。Go1.18将包含很多泛型相关的标准库,包括对现有标准库的泛型支持,这是目前Go官方的一项重要工作。今天就开始吧,以后会继续分享更多关于Go泛型的内容,让大家提前掌握Go泛型。本文转载自微信公众号「polarisxu」,可通过以下二维码关注。转载本文请联系polarisxu公众号。