在用Go时发现的一个小坑

在Python中,返回一个去除原列表中下标为i的元素的新列表,能够用切片语法,将下标i前后的列表切开再组合成一个新的切片,这是不言而喻的。

list1=[1,2,3,4,5]list2=list1[:2]+list1[3:]print(list1,list2)# [1, 2, 3, 4, 5] [1, 2, 4, 5]

但在Go中,仿佛没有这么想当然。如果想当然的用append()去切割合并切片,会影响原来的切片。

slice1 := []int{1, 2, 3, 4, 5}fmt.Println(slice1)slice2 := append(slice1[:2], slice1[3:]...)fmt.Println(slice2)fmt.Println(slice1)// 输入// [1 2 3 4 5]// [1 2 4 5]// [1 2 4 5 5]

为什么会这样?首先,Go的切片只是个构造体,蕴含了一个指向底层数组的指针、长度、容量。当咱们应用切片语法b:=a[low:high]时,新的切片b只是获取了一个构造体,蕴含指向a底层数组的指针,并且可能有不同的长度和容量。

关键在于这个指向a底层数组的指针,这意味着,go中的切片,只是对某个数组的援用。对某个切片或数组重复进行切割,产生的切片只是对同一个底层数组的援用,他们之间会相互影响。

其次咱们要理解append()函数做了什么

func append(slice []Type, elems ...Type) []Type

append会承受一个切片slice,而后是一系列元素。如果slice增加元素后超过本身容量,就会产生扩容(扩容的具体规定这里并不探讨),append会申请一个新的更大的数组。最初返回一个新切片,新切片指向一个新的底层数组。这个是大部分状况下用append()时的场景。

然而如果slice增加元素后没有超过容量会怎么样?那返回的新切片就会持续应用原来的底层数组。在某些状况下,比方当你通过原切片返回一个去除某个元素的新切片,并且不影响原切片。当你应用了这样的语法,就会出错

slice2 := append(slice1[:2], slice1[3:]...)

这里产生了什么?用图表示意的话

本来slice1是这样的

通过slice2 := append(slice1[:2], slice1[3:]...)

append()收到slice1[:2],这是一个和slice1共用底层数组的切片。指针指向slice1的第一个元素,长度为2,容量为5。(容量的意思就是,一个切片的第1个元素,到底层数组的最初1个元素,一共有多少个元素。)
append()收到的第二个参数是slice1[3:]...,这意味着,一一增加slice1从下标3开始的每一个元素,这里是45
最初,slice2增加了2个元素,从长度2增长到了长度4。也就是没有超过容量,因而没有产生扩容。所以slice2依然应用slice1的底层数组,造成了对slice1的影响。

那该怎么办呢?

广泛的解决方案是,结构新切片,而后复制原切片的局部数据到新切片。
具体如何结构,能够先创立空切片,而后用append()增加元素,空切片容量为0,这会导致append创立新的底层数组,不会影响原切片。咱们能够输入这两个切片的首元素地址看看。

slice1 := []int{1, 2, 3, 4, 5}fmt.Printf("slice1,%v%T%p\n", slice1, slice1, slice1)slice2 := append([]int(nil),slice1[:2]...)slice2 = append(slice2,slice1[3:]...)fmt.Printf("slice2,%v%T%p\n", slice2, slice2, slice2)fmt.Printf("slice1,%v%T%p\n", slice1, slice1, slice1)// 输入// slice1,[1 2 3 4 5][]int0xc000018330// slice2,[1 2 4 5][]int0xc00001a2a0// slice1,[1 2 3 4 5][]int0xc000018330