1136 字
约 3 分钟
6
S09-B04 · 数组与切片:切片的本质是三字段
2024-12-31
2026-11-07

适用读者:被"改了切片为什么原数组也变"坑过的人。
前置: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. 自检

  1. 三类字段分别是什么?cap 由谁决定?
  2. b := a[1:3]; b[0]=99 后 a 是什么?
  3. append 什么时候不共享底层数组?
  4. 数组与切片在传参时的差别?
  5. 为什么推荐用 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/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。