乐趣区

关于golang:Go-在不影响原切片情况下返回一个去除某个元素的新切片

在用 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
退出移动版