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

去切片只需要这个!

时间:2023-03-15 17:04:33 科技观察

本文转载自微信公众号《盼盼编程》,作者:盼盼编程。转载本文请联系盼盼编程公众号。前言大家好,我是盼盼!切片是golang中非常重要的一种数据结构。在日常工作和面试中都会遇到,切片需要注意的点也很多。只有深入了解,才能避免挖坑。让我们开始开车吧。Array数组是一种内置类型,它是同一类型数据的集合。是值类型,通过从0开始的下标索引访问元素值,初始化后长度固定,不可修改。当作为方法参数传递时,将制作数组的副本而不是引用相同的指针。数组的长度也是其类型的一部分,其长度可以通过内置函数len(array)获得。还有几点需要注意:Go中的数组是值类型。如果将一个数组赋值给另一个数组,实际上是在复制整个数组。Go中如果使用数组作为函数的参数,实际传递的参数是数组的副本,而不是指向数组的指针,需要传递数组指针来修改数组的值。数组的长度也是Type的一部分,也就是说[1]int和[2]int是不一样的。//传值,传copyfuncupdateArr(b[3]int){b[0]=3}//传指针,[3]int是一个类型funcupdateArrPoint(b*[3]int){b[0]=3}funcmain(){//两种常用的初始化方法//varb=[...]int{1,2,3}varb=[3]int{1,2,3}updateArr(b)fmt.Println(b)updateArrPoint(&b)fmt.Println(b)//计算数组长度和容量fmt.Println(len(b))fmt.Println(cap(b))}print:[123][323]33SliceGo提供了灵活而强大的内置类型Slices(“动态数组”)。与数组相比,切片的长度不固定,可以添加元素,这可能会增加切片在追加时的容量。slice中有两个概念:一个是len的长度,一个是cap的容量。长度是指已经赋值的最大下标+1,可以通过内置函数len()获取。容量是指一个切片当前可以容纳的最大元素个数,可以通过内置函数cap()获取。切片是引用类型,所以传递切片时会引用同一个指针,修改值会影响其他对象。s:=[]int{1,2,3}//直接初始化切片s:=arr[:]//用数组初始化切片s=make([]int,3)//进行初始化,有都是3个元素slice,len和cap都是3s=make([]int,2,3)//初始化,slice有2个元素,len为2,cap为3a=append(a,1)//append1个元素a=append(a,1,2,3)//追加多个元素,手写解包方法a=append(a,[]int{1,2,3}...)//追加一个slice,切片需要解包。但是需要注意的是,在容量不足的情况下,append操作会引起内存重新分配,可能会产生巨大的内存分配和拷贝数据的开销。a=append([]int{0},a...)将元素添加到切片头。一开始一般会导致重新分配内存,会导致所有存在的元素都被复制一次。因此,从切片开头追加元素的性能通常比从末尾追加元素差得多。//Slice是地址传funcupdateSlice(a[]int){a[0]=3}funcmain(){//Slicevara=[]int{1,2,3}c:=make([]int,5)copy(c,a)updateSlice(c)fmt.Println(c)}print[32300]slice的内部实现slice是一个很小的对象,它执行一个抽象并提供相关的操作方法。切片是一个包含三个字段的数据结构,其中包含了Golang对底层数组进行操作所需要的元数据:这三个字段分别是指向底层数组的指针、切片访问的元素个数(即长度)和允许的增长量slice到达的元素数量(即容量)。nil和空切片有时,程序可能需要声明一个值为nil的切片(也称为nil切片)。只要在没有任何初始化的情况下声明它,就会创建一个nil切片。varnum[]intnilslice是Golang中非常常用的创建切片的方式。nil切片可以与许多标准库和内置函数一起使用。当您需要描述不存在的切片时,nil切片很有用。例如,当一个函数期望返回一个切片并且发生异常时。下图描述了nilslice的状态:空slice与nilslice略有不同,以下代码分别通过make()函数和字面值创建了一个空slice:num:=make([]int,0)//使用make创建一个空整数切片num:=[]int{}//使用切片字面量创建一个空整数切片。空切片的底层数组包含0个元素,不分配任何存储空间。当你想表示一个空集合时,时空切片很有用,例如,当数据库查询返回0个查询结果时。在nil切片或空切片上调用内置函数append()、len()和cap()具有相同的效果。通过切片创建一个新的切片切片之所以被称为切片是因为创建一个新的切片意味着切掉底层数组的一部分。通过切片创建新切片的语法如下:slice[i:j]slice[i:j:k]其中i表示从切片的哪个元素开始切片,j控制切片的长度(j-i),k控制切片的容量(k-i),如果不给定k,表示切到底层数组的末尾。下面是几种常见的简写形式:slice[i:]//从i切到末尾slice[:j]//从头切到j(不包括j)slice[:]//从头切到end,相当于复制了整个切片。让我们通过下面的例子来理解通过切片创建新切片的本质://创建一个整数切片//它的长度和容量都是5个元素num:=[]int{1,2,3,4,5}//创建一个新的slice//它的长度是2个元素,容量是4个元素myNum:=slice[1:3]执行上面的代码后,我们有两个slice,它们共享同一个底层数组,但是通过不同的slice,会看到底层数组的不同部分:注意:截取新切片时的原则是“左包含右排除”。所以myNum是从num的index=1截取的,截取index=3之前的元素,即不包含index=3的元素。因此,新的myNum是由num中的第二个元素和第三个元素组成的一个新的切片结构,长度为2,容量为4。切片num看到的是底层数组全部5个元素的容量,而myNum看到的是底层数组的容量只有4个元素。num无法访问基础数组的第一个元素。因此,对于myNum,该元素根本不存在。关于共享底层数组的切片的注意事项:现在两个切片num和myNum共享相同的底层数组。如果一个切片修改了底层数组的共享部分,另一个切片也能感知到://修改myNum的索引为1的元素//同时修改原切片的索引为2的元素nummyNum[1]=35赋值35到myNum的索引为1的元素也在修改索引为num为2的元素:切片只能访问其长度内的元素,切片只能访问其长度内的元素,尝试访问超出其长度的元素会导致语言运行时例外。在使用这部分元素之前,必须将其合并到切片的长度中。下面的代码尝试给num中的元素赋值://修改newNum中索引为3的元素//ThiselementdoesnotexistfornewNumnewNum[3]=45上面的代码会编译,但会生成运行时error:panic:runtimeerror:indexoutofrange切片扩展与数组相比,使用切片的一个好处是可以根据需要增加切片的容量。Golang内置的append()函数处理所有添加长度的操作细节。要使用append()函数,您需要一个要操作的切片和一个要追加的值。当append()函数返回时,返回一个包含修改后结果的新切片。函数append()总是增加新切片的长度,容量可能会改变也可能不会改变,这取决于正在操作的切片的可用容量。num:=[]int{1,2,3,4,5}//创建一个新的slice,长度为2个元素,容量为4个元素myNum:=num[1:3]//使用原来的容量toallocateanewelement//Assignthenewelementto60myNum=append(myNum,60)执行上面代码后的底层数据结构如下图所示:此时,因为myNum底层还有额外的可用容量array,append()函数将可用元素合并到切片的长度中并为其赋值。由于它与原始切片共享相同的底层数组,因此myNum中索引为3的元素的值也发生了变化。如果切片的底层数组没有足够的可用容量,append()函数会创建一个新的底层数组,将引用的现有值复制到新数组中,然后追加新值。此时append操作也会增加slice的长度和容量://创建一个长度和容量为4的整数slicenum:=[]int{1,2,3,4}//追加一个新的元素totheslice//Assignthenewelementto5myNum:=append(num,5)当这个append操作完成后,newSlice有一个全新的底层数组,容量是原来的两倍:函数append()会智能处理增长底层数组。当切片的容量小于1000个元素时,容量总是成倍增加。一旦元素数量超过1000,容量增长因子将被设置为1.25,即容量每次增加25%(这个增长算法可能会随着语言的演进而变化)。总结切片为我们提供了一种操作集合类型数据的便捷方式,并且可以在函数之间高效传递,因此切片类型在代码中被广泛使用。