Go 语言切片(Slice)完全指南

1. Slice 的本质结构

Slice 是变长序列,由三个部分组成:

1
2
3
4
5
6
7
┌─────────┬─────────┬─────────┐
│ 指针 │ 长度 │ 容量 │
│ (ptr) │ (len) │ (cap) │
└────┬────┴─────────┴─────────┘


[ 底层数组 ... ]
字段 说明
指针 (ptr) 指向底层数组中 slice 可访问的第一个元素
长度 (len) slice 中的元素个数
容量 (cap) 从 slice 起点到底层数组末尾的元素总数

2. 数组与切片的声明区别

1
2
3
4
5
6
7
8
// 数组(固定长度)
a := [5]int{1, 2, 3, 4, 5} // [5]int
months := [...]string{1: "Jan", 2: "Feb", 12: "Dec"} // [13]string,编译器推断长度

// 切片(动态长度)
s := []int{1, 2, 3, 4, 5} // []int
s := make([]int, 5) // len=5, cap=5
s := make([]int, 0, 10) // len=0, cap=10

记忆口诀:方括号里有内容([3][...])是数组,空的 [] 才是切片


3. 从数组上切片 s[i:j]

1
2
3
4
months := [...]string{1: "Jan", 2: "Feb", /*...*/ 12: "Dec"} // 数组 [13]string

Q2 := months[4:7] // 切片 []string len=3, cap=9
summer := months[6:9] // 切片 []string len=3, cap=7

切片规则:

  • i 省略默认 0j 省略默认 len(s)
  • 超出 cappanic
  • 超出 len 但在 cap 内则是扩展切片范围(合法)

4. 共享底层数组(⚠️ 重点)

多个 slice 可以引用同一个底层数组,修改会相互影响:

1
2
3
a := []int{1, 2, 3, 4, 5}
b := a[1:3] // [2, 3],与 a 共享底层数组
b[0] = 99 // a 变成 [1, 99, 3, 4, 5] ⚠️

💡 建议:若需隔离修改,使用 copy 创建独立副本。


5. nil slice vs 空 slice

1
2
3
var s []int          // nil slice:  len=0, cap=0, s == nil ✅
s = []int{} // 空 slice: len=0, cap=0, s == nil ❌
s = make([]int, 0) // 空 slice: len=0, cap=0, s == nil ❌

判空最佳实践:判断 slice 是否为空,应该用 len(s) == 0,而不是 s == nil


6. append 函数(⚠️ 核心)

1
2
3
4
var s []int
s = append(s, 1) // ✅ 必须接收返回值!
s = append(s, 2, 3, 4) // 追加多个元素
s = append(s, other...) // 追加另一个 slice

扩容机制:

场景 行为
容量足够 在原底层数组上追加,仍然共享底层数组
容量不足 分配新的更大底层数组(通常翻倍),数据拷贝过去,与原数组不再共享

7. copy 函数

1
2
dst := make([]int, 3)
n := copy(dst, src) // 返回实际复制的元素个数 = min(len(dst), len(src))

💡 copy 是安全复制切片内容的标准方式,可避免共享底层数组带来的副作用。


8. Slice 不可比较

1
2
3
4
a := []int{1, 2}
b := []int{1, 2}
// a == b ❌ 编译错误!
// 唯一合法比较:s == nil

⚠️ 因此 slice 不能作为 map 的 key。若需比较内容,请手动遍历或使用 reflect.DeepEqual


9. 常见惯用法

1
2
3
4
5
6
7
8
9
10
11
// 模拟栈
stack = append(stack, v) // push
top := stack[len(stack)-1] // peek
stack = stack[:len(stack)-1] // pop

// 删除第 i 个元素(保持顺序)
s = append(s[:i], s[i+1:]...)

// 删除第 i 个元素(不保持顺序,更高效)
s[i] = s[len(s)-1]
s = s[:len(s)-1]

🔑 关键要点速记

要点 说明
三要素 指针 + 长度 + 容量
[...] vs [] [...] 是数组(编译器推断长度),[] 是切片
共享底层 切片操作不复制,修改会互相影响
append 必须赋值 s = append(s, v),不能省略 s =
扩容后独立 append 触发扩容后,新旧 slice 不再共享
判空用 len len(s) == 0,不要用 s == nil
不可比较 只能与 nil 比较,不能互相 ==

📌 一句话总结:Slice 是 Go 中灵活高效的动态序列,理解其底层数组共享机制和扩容行为,是写出正确、高性能代码的关键。