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} months := [...]string{1: "Jan", 2: "Feb", 12: "Dec"}
s := []int{1, 2, 3, 4, 5} s := make([]int, 5) s := make([]int, 0, 10)
|
记忆口诀:方括号里有内容([3] 或 [...])是数组,空的 [] 才是切片。
3. 从数组上切片 s[i:j]
1 2 3 4
| months := [...]string{1: "Jan", 2: "Feb", 12: "Dec"}
Q2 := months[4:7] summer := months[6:9]
|
切片规则:
i 省略默认 0,j 省略默认 len(s)
- 超出
cap 会 panic
- 超出
len 但在 cap 内则是扩展切片范围(合法)
4. 共享底层数组(⚠️ 重点)
多个 slice 可以引用同一个底层数组,修改会相互影响:
1 2 3
| a := []int{1, 2, 3, 4, 5} b := a[1:3] b[0] = 99
|
💡 建议:若需隔离修改,使用 copy 创建独立副本。
5. nil slice vs 空 slice
1 2 3
| var s []int s = []int{} s = make([]int, 0)
|
判空最佳实践:判断 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...)
|
扩容机制:
| 场景 |
行为 |
| 容量足够 |
在原底层数组上追加,仍然共享底层数组 |
| 容量不足 |
分配新的更大底层数组(通常翻倍),数据拷贝过去,与原数组不再共享 |
7. copy 函数
1 2
| dst := make([]int, 3) n := copy(dst, src)
|
💡 copy 是安全复制切片内容的标准方式,可避免共享底层数组带来的副作用。
8. Slice 不可比较
1 2 3 4
| a := []int{1, 2} b := []int{1, 2}
|
⚠️ 因此 slice 不能作为 map 的 key。若需比较内容,请手动遍历或使用 reflect.DeepEqual。
9. 常见惯用法
1 2 3 4 5 6 7 8 9 10 11
| stack = append(stack, v) top := stack[len(stack)-1] stack = stack[:len(stack)-1]
s = append(s[:i], s[i+1:]...)
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 中灵活高效的动态序列,理解其底层数组共享机制和扩容行为,是写出正确、高性能代码的关键。