Go 切片删除元素:底层数组真的“跳过”了一格吗?
在 Go 里删除切片中的某个元素,最常见的写法是这样一行:
1 | nums := []int{10, 20, 30, 40, 50} |
运行后 nums 变成了 [10 20 40 50],30 不见了。
很多人第一次看到这行代码都会卡住:数组是一段连续的内存,怎么能“跳过”中间一格?
问一:底层数组真的跳过了一格吗?
没有,数组不能凭空少一格。实际上是后面的元素被逐个往前复制,把要删的那个覆盖掉了。
第 1 步:初始状态

第 2 步:切出两个窗口
nums[:2] 和 nums[3:] 都不是新数组,只是指向同一个底层数组的两个“窗口”:

注意 nums[:2] 的长度是 2,但容量是 5。
第 3 步:append 写入第 1 个元素,40 → 索引 2

原来的 30 被 40 直接覆盖掉了,并没有被跳过。
第 4 步:append 写入第 2 个元素,50 → 索引 3

后面的元素整体往前挪了一格。
第 5 步:返回新切片

append 返回一个长度为 4 的切片,所以你看到的是 [10 20 40 50]。
问二:为什么 append 不新建一个数组?
因为容量够用。nums[:2] 的长度是 2,容量是 5。append 要往里追加 2 个元素,2 + 2 = 4,没超过容量 5,所以它不分配新内存,直接从原数组的索引 2 开始写入。删除也就是在原数组上完成的。
问三:被删的 30 去哪了?末尾的 50 又是怎么回事?
30 被 40 覆盖,已经不存在了。底层数组始终是 5 格,最后一格还留着旧的 50,只是落在了新切片的范围之外,所以看不到。
可以写段代码验证:
1 | nums := []int{10, 20, 30, 40, 50} |
所以要注意:如果还有别的切片共用这个底层数组,它们会看到被改动后的数据。
问四:有更省心的写法吗?
Go 1.21 起,标准库提供了 slices.Delete:
1 | import "slices" |
效果和 append 写法相同,区别是它会把末尾残留的位置清成零值。如果切片里存的是指针,旧对象就不会因为残留的引用而无法回收。
一句话总结
删除不是跳过,是后面的元素整体前移一格,覆盖掉被删的那个,再把切片长度减一。
切片只是指向底层数组的窗口(指针、长度、容量),append 写入的是窗口背后的同一块内存。