如何在Golang中使用slice_Slice基本操作示例

slice是引用类型,其修改是否影响其他slice取决于是否共享底层数组,由cap和操作方式决定;append可能扩容导致原slice失效,必须用返回值更新变量;复制需用copy或append(nil, s...);删除用s=append(s[:i], s[i+1:]...)避免内存泄露。

slice 是引用类型,但底层数组指针可能共享

对 slice 的修改(如 appendcopy、直接赋值元素)是否影响其他 slice,取决于它们是否共用同一底层数组。这不是“一定共享”或“一定不共享”,而是由容量(cap)和操作方式决定的。

常见误判场景:从同一个 slice 切出多个子 slice 后,彼此写入可能互相覆盖——因为它们指向同一段内存。

  • len(s) 获取当前长度,cap(s) 查看可用容量上限
  • 切片表达式 s[i:j:k] 中的 k 显式限制新 slice 的容量,是隔离底层数组最简单的方式
  • append 超出 cap 会分配新底层数组,原 slice 不受影响;但若未超出,就仍在原数组上操作

append 之后原 slice 可能失效

这是 Go 中最常踩的坑之一:append 返回的是新 slice,而原变量仍指向旧 header(含旧 len/cap/ptr)。如果后续继续用原变量读写,可能读到过期数据,或写入被丢弃。

numbers := []int{1, 2}
a := numbers
b := append(numbers, 3) // 分配新底层数组(因 cap==2,append 后需扩容)
numbers = b                // 必须显式更新原变量
// 此时 a 仍指向旧数组 [1 2],而 numbers 指向新数组 [1 2 3]
  • 永远用 slice = append(slice, ...) 形式,不要忽略返回值
  • 若不确定是否扩容,可提前用 make([]T, 0, expectedCap) 预分配足够容量
  • 调试时打印 &s[0](首元素地址)可快速判断是否发生底层数组重分配

复制 slice 要用 copy,不是赋值

直接 newSlice = oldSlice 只是复制了

slice header(指针+长度+容量),两者仍共享底层数组。真正独立副本必须用 copyappend 构造。

src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src) // ✅ 独立副本
// 或
dst = append([]int(nil), src...) // ✅ 同样有效,且更简洁
  • copy(dst, src) 返回实际拷贝的元素个数,等于 min(len(dst), len(src))
  • dst 必须已初始化(不能为 nil),否则 panic
  • append([]T(nil), s...) 是惯用写法,利用了 append 对 nil slice 的特殊处理

删除元素要小心越界和容量泄露

Go 没有内置 delete 函数用于 slice,常用模式是“覆盖+截断”。错误做法会导致内存无法释放(底层大数组被小 slice 持有)或索引越界。

// 删除索引 i 处元素(安全版)
s = append(s[:i], s[i+1:]...) // ✅ 自动处理边界,且不保留原数组引用

// 危险写法示例:
// s = s[:i] + s[i+1:] // ❌ 可能触发两次底层数组复制,性能差
// s = s[:i+copy(s[i:], s[i+1:])] // ❌ 容易算错长度,且未处理 i == len(s)-1
  • s[:i] + s[i+1:]... 本质是两次切片再 append,语义清晰且安全
  • 若频繁删除,考虑用 map 记录逻辑删除标记,避免反复移动内存
  • 若 slice 曾从大数组切出(如 big[100:110]),删除后仍持有整个 big,此时应显式复制:s = append([]int(nil), s...)
底层数组生命周期由所有引用它的 slice 共同决定,哪怕只留一个极小的 slice,也可能让几 MB 的底层数组无法 GC。这是最隐蔽也最难调试的问题。