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

golang中array和slice有什么区别?

时间:2023-03-15 00:03:58 科技观察

数组号称是“定长定型的序列集合”,但是在golang中引入了“切片”,语法上看起来和数组很像。为什么要介绍这些?切片和数组有什么区别?接下来,让我们一一了解。Array数组定义了一个数组vararr[5]int=[5]int{1,2,3,4,5}上面的语句是说我们定义一个变量arr为5个int的数组类型,也就是[5]int,同时给value赋初值1,2,3,4,5。内存分布如图所示,数据结构集合紧密地结合在一起。注意如果定义数组的方法是;arr:=new([4]int),那么arr的数据类型是*[4]int而不是[4]int;变长数组,当然如果数组的长度4不固定,可以用...代替q:=[...]int{1,2,3}arrayloop数组循环有一个golang中独特的语法,它是forrangevararr[4]int=[4]int{1,2,3,4}fori,v:=rangearr{fmt.Printf("数组中的%v项,thevalueis%v\n",i,v)}//输出结果数组中的第0项,值为数组1中的1项,值为2数组中的第2项,值为3数组中的第3项,值为该数组的4个常用方法常用方法为“len()”方法和“cap()”方法;len()方法的作用是获取数组或切片的“长度”。cap()方法的作用是获取数组或者切片的“容量”,但是“在数组中,这两个值总是一样的”,这里就不多想了.将在后面的章节中详细阐述。为什么切片有切片?切片之所以诞生,是因为golang中数组存在两大问题。固定长度,也就是说初始化数组后,不能再压入超过len(array)长度的元素。当数组作为函数之间的参数传递时,按值传递相当于复制数据,对性能有很大的浪费。切片数据类型底层结构typeslicestruct{arrayunsafe.Pointer//指向数组的指针lenint//当前切片的长度capint//当前切片的容量}比如我们定义一个切片:=make([]int,3,5)s[0]=1s[1]=2s[2]=3那么上述变量在内存中的数据结构如下图所示,所以可以从上面的分析可以看出“切片依赖于数组,它是指向数组的指针”。由于slice是指针类型,所以作为参数传递时必须是引用类型。无需重新拷贝造成空间浪费。切片的拦截我们上面说了切片依赖于数组,所以切片的拦截是基于数组的。让我们看看这个拦截的例子。看例子的时候记住一个原则:“左包含,右不包含”。a1:=[...]int{1,2,3,4,5,6,7,8,9}s4:=a1[2:4]//输出结果[34]s5:=a1[:4]//输出结果[1234]s6:=a1[2:]//输出结果[3456789]s7:=a1[:]//输出结果[123456789]以上例子均符合上述“左包右排原则”s4截取自下标2截取至下标4;s5省略第一个参数,表示从下标0开始截取;s6省略第二个参数,表示截取最后一个元素;s7省略两个参数,只填中间的冒号:,表示取所有元素;slice的长度len()和capacity的长度cap()很容易理解,简单理解就是“元素个数”,capacity相对难理解“从slice的第一个元素到切片引用的底层数组中数组的最后一个元素是切片的容量”。我们直接看例子:a1:=[...]int{1,2,3,4,5,6,7,8,9}s5:=a1[:4]//[1234]s6:=a1[2:]//[3456789]s7:=a1[:]//[123456789]fmt.Printf("len(s5):%dcap(s5):%d\n",len(s5),cap(s5))//49fmt.Printf("len(s6):%dcap(s6):%d\n",len(s6),cap(s6))//77fmt.Printf("len(s7):%dcap(s7):%d\n",len(s7),cap(s7))//99a1是数组的长度是9,容量也是9,取值从1到9s5/s6/s7是对数组a1进行切割得到的切片。s5的长度为4,因为1234只有4个元素,容量为9,因为s5的切片是从数组起始位置开始切的:第一个元素为1,最后一个元素为s5的底层数组a1为9,从1到9共有9个元素,所以s5的容量为9。s6的长度为7,因为s6的元素为39的7个元素;容量也是7,因为s5的底层数组最后一个元素是9,而39一共有7个元素,所以s6的容量是7。s7比较好理解,长度和容量都是9,请自己理解。切片的常用方法makemake方法主要用于生成切片,比较简单。例如下面的例子是定义一个长度为5,容量为10的slice。s1:=make([]int,5,10)fmt.Printf("s1:%vlen(s1):%dcap(s1):%d\n",s1,len(s1),cap(s1))//输出结果//s1:[00000]len(s1):5cap(s1):10appendappend主要用于追加切片。让我们直接看例子。vars=[]int{1,2,3,4}fmt.Println(s)fmt.Printf("len:%d,cap:%d",len(s),cap(s))//输出结果[1234]len:4,cap:4我们可以看到定义了一个slice,初始化了4个元素,此时slice的长度和容量都是4。vars=[]int{1,2,3,4}s=append(s,5)//追加一个元素5到slicesfmt.Println(s)fmt.Printf("len:%d,cap:%d\n",len(s),cap(s))//输出结果[12345]len:5,cap:8分析:长度从4变为5,我们很好理解;为什么capacity会从4变成8?》这是因为go语言对切片的自动扩容机制,添加了append,如果cap不够用,go的底层会替换底层数组,是go语言的一套扩容策略。“简单来说,这个扩容机制就是“如果不够,就在前面的基础上加倍,如果超过1M,就+1M”,这和redis的bitmap类型的扩容机制是一样的。slice展开的“坑”funcmain(){vars=[]int{1,2,3}modifySlice(s)fmt.Println(s)//print[123]}funcmodifySlice(s[]int){s=append(s,4)s[0]=4}这种陷阱在面试中经常遇到。当slice作为函数参数时,“Ifexpansionoccursinsidethefunction,thenmodifythesliceinthevalueisnotvalid”,因为修改发生在新数组内存中,对旧数组内存没有影响。如何追加多个元素:=[]int{1,2,3,4}s2:=[]int{5,6}s3:=append(s1,s2...)//...表示拆包,将slice的值作为附加元素fmt.Println(s3)//输出结果//[123456]copy//定义slices1s1:=[]int{1,2,3}//th方式一:直接声明变量=赋值//s2slice和s1引用同一个内存地址vars2=s1//方式二:copyvars3=make([]int,3)copy(s3,s1)///使用复制函数将参数2的元素复制到参数1中s1[0]=11fmt.Printf("s1:%vs2:%vs3:%v",s1,s2,s3)//s1和s2are[1123]s3is[123]我们发现s1和s2都是[1123]s3是[123],说明copy方法是复制一份,开辟新的内存空间,并且不再引用s1的内存地址,这就是两者的区别。如果您觉得这篇文章还不错,记得关注点赞哦,您的支持是我创作最大的动力。