本文转载自微信公众号《架构提升之路》,作者的架构精修之路。转载本文请联系建筑改良之路公众号。1.数组数组是特定类型的固定长度元素的序列,数组可以由零个或多个元素组成。因为数组的长度是固定的,所以在Go中很少直接使用数组。数组对应的类型是Slice(切片),它是一个可以增长和收缩的动态序列,切片功能更加灵活。数组的每个元素都可以通过索引下标访问,索引下标的范围从0到数组长度减1。内置的len函数会返回数组中元素的个数。vara[3]int//arrayof3integersfmt.Println(a[0])//printthefirstelementfmt.Println(a[len(a)-1])//打印thelastelement,a[2]默认情况下,数组的每个元素是初始化为元素类型对应的零值,对于数值类型为0。varq[3]int=[3]int{1,2,3}varr[3]int=[3]int{1,2}fmt.Println(r[2])//如果在数组中则为“0”长度位置出现“...”省略号,表示数组的长度是根据初始化值的个数计算的。因此,上述q数组的定义可以简化为:q:=[...]int{1,2,3}fmt.Printf("%T\n",q)//[3]int"array长度是数组类型的组成部分,所以[3]int和[4]int是两种不同的数组类型。数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定。q:=[3]int{1,2,3}q=[4]int{1,2,3,4}//compileerror:cannotassign[4]intto[3]int如果数组的元素类型是OK相互比较,那么数组类型也可以相互比较。这时候我们可以直接通过==比较运算符来比较两个数组。只有当两个数组的所有元素都相等时,数组才相等。不等式比较运算符!=遵循相同的规则。a:=[2]int{1,2}b:=[...]int{1,2}c:=[2]int{1,3}fmt.Println(a==b,a==c,b==c)//"truefalsefalse"d:=[3]int{1,2}fmt.Println(a==d)//编译错误:无法比较[2]int==[3]int2。切片(Slice)切片(slicing)表示一个可变长度的序列,其中每个元素都具有相同的类型。切片类型一般写成[]T,其中T表示切片中元素的类型;切片的语法与数组的语法非常相似,只是它没有固定长度。切片是一种轻量级数据结构,它提供对数组子序列(或全部)元素的访问,切片的底层确实引用数组对象。切片由三部分组成:指针、长度和容量。指针指向第一个切片元素对应的底层数组元素的地址。需要注意的是,切片的第一个元素不一定是数组的第一个元素。长度对应于切片中元素的数量;长度不能超过容量,一般是从切片开始到底层数据结束。内置的len和cap函数分别返回切片的长度和容量。一个字符串数组,表示一年中月份的名称,两个切片重叠对该数组的引用。数组定义如下:months:=[...]string{1:"January",/*...*/,12:"December"}所以一月是月[1],十二月是月[12].通常,数组的第一个元素从索引0开始,但月份通常从1开始,所以我们在声明数组时直接跳过第0个元素,第0个元素会自动初始化为空字符串。slice的切片操作s[i:j],其中0≤i≤j≤cap(s),用于创建一个新的slice,引用s从第i个元素开始到第j-1个元素的子序列.新切片将只有j-i个元素。如果省略位置i的索引,则使用0代替,如果省略位置j的索引,则使用len(s)代替。因此months[1:13]分片操作会引用所有有效的月份,相当于months[1:]操作;months[:]切片操作引用整个数组。让我们分别定义代表第二季度和北方夏季月份的切片,有重叠的部分:Q2:=months[4:7]summer:=months[6:9]fmt.Println(Q2)//["April""May""June"]fmt.Println(summer)//["June""July""August"]两个切片都包含June。append函数append函数用于向切片添加元素:varrunes[]runefor_,r:=range"Hello,world"{runes=append(runes,r)}fmt.Printf("%q\n",runes)//"['H''e''l''l''o'',''''世界''世界']"为了提高内存使用效率,新分配的数组一般比保存x和y所需的最小大小。通过每次扩展数组时直接加倍长度,避免了多次内存分配,同时也保证了添加单个元素操作的平均时间为常数时间。该程序演示了效果:funcmain(){varx,y[]intfori:=0;i<10;i++{y=appendInt(x,i)fmt.Printf("%dcap=%d\t%v\n",i,cap(y),y)x=y}}//每次容量变化都会引起内存的重新分配和复制操作:0cap=1[0]1cap=2[01]2cap=4[012]3cap=4[0123]4cap=8[01234]5cap=8[012345]6cap=8[0123456]7cap=8[01234567]8cap=16[012345678]9cap=16[0123456789]我们来看i=3次迭代。当时x包含三个元素[012],但是容量是4,所以可以简单的在末尾添加新的元素,不需要分配新的内存。那么新的y的长度和容量为4,并且与x引用相同的底层数组,如图4.2所示。在下一次迭代i=4时,现在没有新的空闲空间,所以appendInt函数分配一个容量为8的底层数组,将x[0123]的4个元素复制到新空间的开头,并添加新元素i,新元素的值为4。新y的长度为5,容量为8;后面有3个空闲位置,3次迭代不需要分配新的空间。在当前迭代中,y和x是对应于不同底层数组的视图。该操作如图4.3所示。内置的append函数可能会使用比appendInt更复杂的内存扩展策略。因此,我们通常不知道是不是append调用导致了内存的重新分配,也就无法确认新的slice和原来的slice是否引用了同一个底层数组空间。同样,我们无法确定对原始切片的操作是否会影响新切片。
