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

4次阅读

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

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

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 构造。

立即学习 go 语言免费学习笔记(深入)”;

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。这是最隐蔽也最难调试的问题。

text=ZqhQzanResources