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

GoGeneric系列:Slices包详解

时间:2023-03-16 00:36:28 科技观察

大家好,我是polarisxu。前段时间,RussCox澄清了与泛型相关的事情。他本来打算把泛型相关的包加入标准库,放在golang.org/x/exp下。目前Go泛型的主要设计者ianlancetaylor已经完成了slice和maps包的开发,代码已经提交到golang.org/x/exp。经过使用、讨论等,以及社区的认可,有望在1.19中期纳入标准库。今天通过学习slices包来掌握Go泛型的使用。01为什么要添加slices包标准库中有bytes和strings包,分别用来处理[]byte和string类型,提供了很多方便的功能,但是对于普通的slice,没有相关的包可以使用。比如bytes和strings都有Index函数,用来在[]byte或string中查找一个byte或string的索引。对于普通切片,没办法自己写一大堆包来处理,用户只能自己实现。这也是没有泛型的缺点。提供字节和字符串的主要原因是它们经常被使用。既然有了泛型,就可以实现一些方便的切片操作方法了。需要针对特定??类型的切片实现相同的功能。02constraints包在继续讲解slices包之前,我们先来看看constraints包。这个包为类型参数(泛型)定义了一组有用的约束。该包已被确认包含在Go1.18标准库中。截至目前(2021.11.27),此包定义了6种约束类型://Signedisaconstraintthatpermitsanysignedintegertype.//IffuturereleasesofGoaddnewpredeclaredsignedintegertypes,//thisconstraintwillbemodifiedtoincludethem.typeSignedinterface{~int|~int8|~int16|~int32|~int64}//Unsignedisapermiteintegertypeunstraintthat/IffuturereleasesofGoaddnewpredeclaredunsignedintegertypes,//thisconstraintwillbemodifiedtoincludethem.typeUnsignedinterface{~uint|~uint8|~uint16|~uint32|~uint64|~uintptr}//Integerisaconstraintthatpermitsanyintegertype.//IffuturereleasesofGoaddnewpredeclaredintegertypes,//thisconstraintwillbemodifiedtoincludethem.typeIntegerinterface{Signed|Unsigned}//Floatisaconstraintthatpermitsanyfloating-pointtype.//IffuturereleasesofGoaddnewpredeclaredfloating-pointtypes,//thisconstraintwillbemodifiedtoincludethem.typeFloatinterface{~float32|~float64}//Complexisaconstraintthatpermitsanycomplexnumerictype。//如果将来发布esofGoaddnewpredeclaredcomplexnumerictypes,//thisconstraintwillbemodifiedtoincludethem.typeComplexinterface{~complex64|~complex128}//Orderedisaconstraintthatpermitsanyorderedtype:anytype//thatsupportstheoperators<<=>=>.//IffuturereleasesofGoaddneworderedtypes,//thisconstraintwillbemodifiedtoincludethem.typeOrderedinterface{Integer|Float|~string}前面3个是Integer相关的类型约束,Float是浮点类型约束,Complex是负类型约束,Ordered表示支持排序的类型约束,支持大小比较的类型。上一篇:《Go泛型系列:Go1.18 类型约束那些事》提到约束语法变了,一个是|符号,一个是~。在上面的定义中,很多地方都用到了~符号。它表示类型本身,底层类型就是它的类型,这也适用于此约束。03slices包详解目前slices包有14个函数,可以分为几组:slice比较元素searchmodifyslicecloneslice其中,modifyslice分为插入元素,删除元素,连续元素去重,切片膨胀和收缩。slice比较比较两个slice中的元素,分为相等和普通比较:funcEqual[Ecomparable](s1,s2[]E)boolfuncEqualFunc[E1,E2any](s1[]E1,s2[]E2,eqfunc(E1,E2)bool)boolfuncCompare[Econstraints.Ordered](s1,s2[]E)intfuncCompareFunc[E1,E2any](s1[]E1,s2[]E2,cmpfunc(E1,E2)int)int其中比较约束是Language-已实施(因为它很常见),表示可比较的约束(比较是否相等)。主要是,E、E1、E2等只是泛型类型表示。定义的时候可以用自己喜欢的,比如T,T1,T2等。看具体的实现:funcEqual[Ecomparable](s1,s2[]E)bool{iflen(s1)!=len(s2){returnfalse}fori,v1:=ranges1{v2:=s2[i]ifv1!=v2{returnfalse}}returntrue}没有什么特别的地方,只是把s1和s2当成相同类型的slice。元素搜索在切片中搜索一个元素,分为普通搜索和包含判断:funcIndex[Ecomparable](s[]E,vE)intfuncIndexFunc[Eany](s[]E,ffunc(E)bool)intfuncContains[Ecomparable](s[]E,vE)bool其中,IndexFunc的类型参数没有使用任何约束(即any),表示通过f参数进行查找,其实现如下:funcIndexFunc[Eany](s[]E,ffunc(E)bool)int{fori,v:=ranges{iff(v){returni}}return-1}参数f是一个接受一个参数的函数,类型为E,这是一个泛型,和IndexFunc的第一个参数类型[]E的元素类型需要保持一致,所以遍历s的元素可以直接传给f。一般不建议修改slice,性能较差。如果这样的需求比较多,可能需要考虑更换数据结构。//在切片的i位置插入一个元素(可以是多个)funcInsert[S~[]E,Eany](sS,iint,v...E)S//删除切片中i到j的元素slice,即删除s[i:j]元素funcDelete[S~[]E,Eany](sS,i,jint)S//将连续相等的元素替换为一个,类似于Unix的uniq命令。compact修改切片的内容,它不会创建新的切片funcCompact[S~[]E,Ecomparable](sS)funcCompactFunc[S~[]E,Eany](sS,eqfunc(E,E)bool)S//增加slice的容量,至少增加n个funcGrow[S~[]E,Eany](sS,nint)S//去掉不用的容量,相当于缩小funcClip[S~[]E,Eany](sS)S上面的类型约束包括两个:S~[]E:表示这是slice的泛型版本,是对slice的约束。注意[]前面的~,表示支持自定义切片类型,比如typemyslice[]intEanyorEcomparable:对上述切片元素类型的约束。克隆切片以获得切片的副本,并复制元素。注意slice中元素的拷贝是浅拷贝,非值类型不会进行深拷贝。funcClone[S~[]E,Eany](sS)S{//Preservenelincaseitmatters.ifs==nil{returnil}returnappend(S([]E{}),s...)}04总结因为存在generics,相同的功能,无需为不同类型的切片编写多个代码。因为有些函数很常用,所以Go官方对它们进行了封装,以后会在标准库中提供。出于谨慎,切片包不会包含在1.18中。如果需要使用切片中的函数,可以使用从切片代码中复制的方法。个人觉得依赖golang.org/x/exp不好。切片源码地址:https://github.com/golang/exp/blob/master/slices/slices.go。