1136 字
约 3 分钟
6
S09-B04 · 数组与切片:切片的本质是三字段
适用读者:被"改了切片为什么原数组也变"坑过的人。
前置:S09-B02。
实测环境:Go 1.22.2(2026-11-05)。
覆盖旧文:A-1454(33) A-1455(92) = 125 点。
覆盖扩展:E-S09-005(切片三字段:ptr/len/cap)。
上一篇:S09-B03 · 常量、iota 与指针
下一篇:S09-B05 · map 与 list。
1. 一句话结论
切片不是"动态数组",而是一个三字段的结构体:{指向底层数组的指针, 长度, 容量}。
slice ──► ┌───────────┐
│ ptr ─────┼──► 底层数组 [1, 2, 3, 4, 5]
│ len = 2 │
│ cap = 4 │
└───────────┘
| 字段 | 含义 | 谁决定 |
|---|---|---|
ptr |
底层数组的起始位置 | 切片表达式 |
len |
当前可见元素数 | high - low |
cap |
从起点到底层数组末尾的可用数 | 底层数组剩余长度 |
"共享"的本质:两个切片如果 ptr 指向同一段内存,改一个就影响另一个。
2. 实测:改切片真的会改原数组
a := []int{1, 2, 3}
b := a[1:3] // 共享底层数组
b[0] = 99
fmt.Println("a =", a, " b =", b, " len(cap):", len(b), cap(b))
实测输出:
a = [1 99 3] b = [99 3] len(cap): 2 2
读法:b[0] 就是 a[1],改 b 等于改 a。b 的 len=2、cap=2(从索引 1 到末尾)。
3. append 与 cap 的增长(实测)
s := make([]int, 0, 2)
for i := 0; i < 9; i++ { s = append(s, i) }
实测 cap 变化:
2->4 4->8 8->16
| 阶段 | cap | 说明 |
|---|---|---|
| 初始 | 2 | make(..., 0, 2) |
| 第 3 个元素 | 4 | 超出 → 分配新数组(容量翻倍) |
| 第 5 个 | 8 | 再翻倍 |
| 第 9 个 | 16 | 再翻倍 |
关键结论:append 一旦超出 cap,就会分配新数组并拷贝——此后的 s 与原底层数组不再共享。
| 写法 | 是否共享 |
|---|---|
append 未超 cap |
✅ 共享(可能覆盖原数组的后续元素!) |
append 超 cap |
❌ 新数组,独立 |
这就是"append 之后原来的切片看不到新元素,但可能被覆盖"的两面性(J01 会详细演示 append 覆盖原数组的陷阱)。
4. 数组 vs 切片:一个容易混的点
| 特性 | 数组 [3]int |
切片 []int |
|---|---|---|
| 长度 | 类型的一部分([3]int ≠ [4]int) |
运行时字段 |
| 传参 | 值拷贝整个数组 | 拷贝三字段(共享底层) |
| 可比较 | ✅(长度相同才能比) | ❌(只能与 nil 比) |
| 追加 | 不支持 | append |
判据:Go 里几乎不用数组,都用切片——除非需要固定长度语义或作为 map 的 key。
5. 常用操作速查
| 操作 | 写法 |
|---|---|
| 创建 | make([]int, 0, 16) / []int{1,2,3} |
| 追加 | s = append(s, x) |
| 追加多个 | s = append(s, xs...) |
| 删除第 i 个 | s = append(s[:i], s[i+1:]...) |
| 复制 | copy(dst, src) |
| 判空 | len(s) == 0(不要用 s == nil,两者语义不同) |
注意:"删除中间元素"用 append 会修改原数组(后续元素前移),如果还有别的切片引用它,会看到变化。
6. 五个误区
| 误区 | 真相 |
|---|---|
| "切片是引用类型" | 它本身是值(三字段结构体),只是内部含指针 |
"append 总是复制" |
未超 cap 时不复制,可能覆盖原数组 |
"len 与 cap 一样" |
len 是可见长度,cap 是可用容量 |
| "变量赋值会深拷贝" | b := a 只拷三字段,底层数组共享 |
"s == nil 与 len(s)==0 等价" |
空切片可能非 nil([]int{}) |
7. 自检
- 三类字段分别是什么?
cap由谁决定? b := a[1:3]; b[0]=99后a是什么?append什么时候不共享底层数组?- 数组与切片在传参时的差别?
- 为什么推荐用
len(s)==0而不是s==nil?
8. 下一篇
S09-B05 · map 与 list:零值、随机遍历与删除
切片讲完,下一篇讲 map:为什么遍历顺序随机、读不存在的 key 为什么返回零值、以及 comma ok 的用法。
本篇属于系列《S09 · Go 语言》。旧文来源:A-1454/A-1455;扩展点 E-S09-005。共享与 cap 序列为 Go 1.22.2 实测。
S09-B04 · 数组与切片:切片的本质是三字段
https://www.clxhxhhr.top/posts/5302/ 评论
0 条
还没有评论,先写一条吧。