您的位置 首页 > 德语词汇

slice是什么意思?Slice

大家好,关于slice是什么意思很多朋友都还不太明白,不过没关系,因为今天小编就来为大家分享关于Slice的知识点,相信应该可以解决大家的一些困惑和问题,如果碰巧可以解决您的问题,还望关注下本站哦,希望对各位有所帮助!

Go语言的slice是用的比较多的,我们需要掌握其原理,避坑。

slice翻译成中文的意思是切片,和数组比较类似,如果出现越界,发出现panic,但是又笔数组灵活,可以自动扩容。

slice是什么意思?Slice

//runtime/slice.go\ntypeslicestruct{\narrayunsafe.Pointer//元素指针\nlenint//长度\ncapint//容量\n}\n

slice的三个属性

创建方式代码示例直接声明varslice[]intnewslice:=*new{[]int}字面量slice:=[]int{1,2,3,4,5}makeslice:=make([]int,5,10)从切片或者数组”截取“slice:=array[1:5]或者slice:=soourceSlice[1:5]

直接声明创建出来的slice是一个nilslice,长度和容量都是0,和nil的比较结果是个true。

创建方式nil切片空切片方式一vars1[]intvars2=[]int{}方式二vars4=*new([]int)vars3=make([]int,0)长度00容量00和nil比较truefalse

nilslice和空slice很相似,长度和容量都是0,官方建议尽量使用nil切片

s1:=[]int{0,1,2,3,8:100}\nfmt.Println(s1,len(s1),cap(s1))\n

运行结果:

[01230000100]99\n

采用的直接赋值的方式,未注明的元素默认值0

make需要传入3个参数:切片类型,长度,容量。容量如果不传,默认和长度相等。

packagemain\n\nimport"fmt"\n\nfuncmain(){\nslice:=make([]int,5,10)//长度为5,容量为10\nslice[2]=2//索引为2的元素赋值为2\nfmt.Println(slice)\n}\n

执行如下命令,得到Go汇编代码

gotoolcompile-Smain.go\nslice和数组区别

slice是底层数据是数组,slice是对数据的封装,描述的是一个数组片段,都可以通过下标访问单个元素。

当原slice容量小于1024的时候,新slice容量变成原来的2倍;原slice容量超过1024,新slice容量变成原来的1.25倍。

packageslice\n\nimport"testing"\n\nimport"fmt"\n\nfuncTestSpread(t*testing.T){\ns:=make([]int,0)\n\noldCap:=cap(s)\n\nfori:=0;i<2048;i++{\ns=append(s,i)\n\nnewCap:=cap(s)\n\nifnewCap!=oldCap{\nfmt.Printf("[%d->%4d]cap=%-4d|afterappend%-4dcap=%-4d\\n",0,i-1,oldCap,i,newCap)\noldCap=newCap\n}\n}\n}\n

运行结果:

===RUNTestSpread\n[0->-1]cap=0|afterappend0cap=1\n[0->0]cap=1|afterappend1cap=2\n[0->1]cap=2|afterappend2cap=4\n[0->3]cap=4|afterappend4cap=8\n[0->7]cap=8|afterappend8cap=16\n[0->15]cap=16|afterappend16cap=32\n[0->31]cap=32|afterappend32cap=64\n[0->63]cap=64|afterappend64cap=128\n[0->127]cap=128|afterappend128cap=256\n[0->255]cap=256|afterappend256cap=512\n[0->511]cap=512|afterappend512cap=1024\n[0->1023]cap=1024|afterappend1024cap=1280\n[0->1279]cap=1280|afterappend1280cap=1696\n[0->1695]cap=1696|afterappend1696cap=2304\n

看到运行结果,当slice容量小于1024时,新slice的容量的确是老slice的2倍,看着没事问题。但是当想slice添加元素1028时,老的slice容量为1280,新的slice容量为1696,并不是1.25倍关系。,1696/1280=1.325。添加完1696后,新容量2304也不是1696的1.25倍,为啥?看源码

//go1.9.5src/runtime/slice.go:82\nfuncgrowslice(et*_type,oldslice,capint)slice{\n//……\nnewcap:=old.cap\ndoublecap:=newcap+newcap\nifcap>doublecap{\nnewcap=cap\n}else{\nifold.len<1024{\nnewcap=doublecap\n}else{\nfornewcap<cap{\nnewcap+=newcap/4\n}\n}\n}\n//……\n\ncapmem=roundupsize(uintptr(newcap)*ptrSize)\nnewcap=int(capmem/ptrSize)\n}\n

貌似说的“长度小于1024扩容2倍,然后大于1024扩容1.25倍”是对的,但是后半部分还有newcap做了一个容量对齐,对齐后,新slice容量要大于等于老slice的2倍或者1.25倍。实际上并不对。

funcTestSpread2(t*testing.T){\ns:=[]int{1,2}\ns=append(s,4,5,6)\nfmt.Printf("len=%d,cap=%d",len(s),cap(s))\n}\n

运行结果是:

len=5,cap=6\n

不是说小于1024要翻倍扩容么,初始化cap容量是2,添加4的时候,容量应该是不够,翻倍应该是4才对,再添加6的时候,容量还是不够,容量应该是8才对啊,为啥结果是6?

//go1.9.5src/runtime/slice.go:82\nfuncgrowslice(et*_type,oldslice,capint)slice{\n//……\nnewcap:=old.cap\ndoublecap:=newcap+newcap\nifcap>doublecap{\nnewcap=cap\n}else{\n//……\n}\n//……\n\ncapmem=roundupsize(uintptr(newcap)*ptrSize)\nnewcap=int(capmem/ptrSize)\n}\n

例子里面s:=[]int{1,2}初始化容量len和cap都是2,s要append三个元素,容量最小要变成3,即cap=5,表示调用growslice函数时,传入的第三个参数应该是5,cap=5,double是原slice容量的2倍,等于4,此时满足第一个if条件newcap=5,

接着调用了roundupsize函数传入了40(代码中ptrSize是指一个指针的大小,在64位机上是8),然后再内存对齐,我们在看下roundupsize源码:

//src/runtime/msize.go:13\nfuncroundupsize(sizeuintptr)uintptr{\nifsize<_MaxSmallSize{\nifsize<=smallSizeMax-8{\nreturnuintptr(class_to_size[size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]])\n}else{\n//……\n}\n}\n//……\n}\n\nconst_MaxSmallSize=32768\nconstsmallSizeMax=1024\nconstsmallSizeDiv=8\n

最终的长度返回是根据class_to_size[size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]]

go中源码有关能i村分配的两个slice。

varsize_to_class8=[smallSizeMax/smallSizeDiv+1]uint8{0,1,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,14,14,15,15,16,16,17,17,18,18,18,18,19,19,19,19,20,20,20,20,21,21,21,21,22,22,22,22,23,23,23,23,24,24,24,24,25,25,25,25,26,26,26,26,26,26,26,26,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31}\n\nvarclass_to_size=[_NumSizeClasses]uint16{0,8,16,32,48,64,80,96,112,128,144,160,176,192,208,224,240,256,288,320,352,384,416,448,480,512,576,640,704,768,896,1024,1152,1280,1408,1536,1792,2048,2304,2688,3072,3200,3456,4096,4864,5376,6144,6528,6784,6912,8192,9472,9728,10240,10880,12288,13568,14336,16384,18432,19072,20480,21760,24576,27264,28672,32768}\n

我们传进去的size等于40。所以(size+smallSizeDiv-1)/smallSizeDiv=5;获取size_to_class8数组中索引为5的元素为4;获取class_to_size中索引为4的元素为48。(smallSizeDiv对应的是8)

newcap=int(capmem/ptrSize)//6\n

这个就是为啥最后slicecap=6的原因了。

nilslice或者emptyslice都是可以通过append进行扩容,最终是调用。malloc来向Go的内存管理器申请到一块内存,然后再赋值给nilslice或者emptyslice,这样nil就变成了真正的slice

当slice作为函数参数时,就是一个普通的结构体。其实很好理解:若直接传slice,在调用者看来,实参slice并不会被函数中的操作改变;若传的是slice的指针,在调用者看来,是会被改变原slice的。

值得注意的是,不管传的是slice还是slice指针,如果改变了slice底层数组的数据,会反应到实参slice的底层数据。为什么能改变底层数组的数据?很好理解:底层数据在slice结构体里是一个指针,尽管slice结构体自身不会被改变,也就是说底层数据地址不会被改变。但是通过指向底层数据的指针,可以改变切片的底层数据,没有问题。

funcTestSliceChange(t*testing.T){\ns:=[]int{1,1,1}\nf(s)\nfmt.Println(s)\n}\n\nfuncf(s[]int){\n//i只是一个副本,不能改变s中元素的值\n/*for_,i:=ranges{\ni++\n}\n*/\n\nfori:=ranges{\ns[i]+=1\n}\n}\n

运行结果:

[222]\n---PASS:TestSliceChange(0.00s)\nPASS\n

果真改变了原始slice的底层数据。这里传递的是一个slice的副本,在f函数中,s只是TestSliceChange函数中s的一个拷贝。在f函数内部,对s的作用并不会改变外层TestSliceChange函数的s。

要想真的改变外层slice,只有将返回的新的slice赋值到原始slice,或者向函数传递一个指向slice的指针。我们再来看一个例子:

funcmyAppend(s[]int)[]int{\n//这里s虽然改变了,但并不会影响外层函数的s\ns=append(s,100)\nreturns\n}\n\nfuncmyAppendPtr(s*[]int){\n//会改变外层s本身\n*s=append(*s,100)\nreturn\n}\n\nfuncTestSliceChange2(t*testing.T){\ns:=[]int{1,1,1}\nnewS:=myAppend(s)\n\nfmt.Println(s)\nfmt.Println(newS)\n\ns=newS\n\nmyAppendPtr(&s)\nfmt.Println(s)\n}\n

运行结果:

===RUNTestSliceChange2\n[111]\n[111100]\n[111100100]\n---PASS:TestSliceChange2(0.00s)\nPASS\n

myAppend函数里,虽然改变了s,但它只是一个值传递,并不会影响外层的s,因此第一行打印出来的结果仍然是[111]。

而newS是一个新的slice,它是基于s得到的。因此它打印的是追加了一个100之后的结果:[111100]。

最后,将newS赋值给了s,s这时才真正变成了一个新的slice。之后,再给myAppendPtr函数传入一个s指针,这回它真的被改变了:[111100100]。

到此,关于slice的部分就讲完了,不知大家有没有看过瘾。我们最后来总结一下:

好了,文章到此结束,希望可以帮助到大家。

本站涵盖的内容、图片、视频等数据,部分未能与原作者取得联系。若涉及版权问题,请及时通知我们并提供相关证明材料,我们将及时予以删除!谢谢大家的理解与支持!

Copyright © 2023