在用 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 开始的每一个元素,这里是 4
和5
。
最初,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