len() 返回切片当前长度(可用元素个数),cap() 返回从起始指针到底层数组末尾的元素个数;二者均为切片头结构体字段的直接读取,零开销。

len() 和 cap() 返回的到底是什么
len() 返回的是你“能用的元素个数”,cap() 返回的是“还能往里塞多少,不用换底层数组”。它们不是估算值,而是切片头结构体里两个整型字段的直接读取——编译器原生支持,零开销。
- 只要没越界,
slice[i]访问的一定是len(slice)范围内的有效数据;超出就 panic -
cap(slice)决定了append()是否需要分配新内存:新长度 ≤ cap → 复用底层数组;否则拷贝+扩容 - 切片截取(如
s = s[2:])会改变len,也会缩容cap——因为起始指针偏移了,底层数组剩余可用空间变少
make([]T, len, cap) 三个参数怎么配才不翻车
很多人以为第三个参数只是“预留空间”,其实它直接绑定底层数组大小和起始偏移。错配会导致意外共享、提前扩容或浪费内存。
-
make([]int, 0, 10):长度 0,容量 10 → 底层数组已分配 10 个int,但len是 0,首次append不触发扩容 -
make([]int, 5, 5):长度=容量=5 → 满了就必扩容,没有缓冲余地 -
make([]int, 5, 10):长度 5,容量 10 → 还能追加 5 个元素不 realloc,适合预估写入量的场景(如解析固定上限日志行) - 省略第三个参数(
make([]int, 5))等价于make([]int, 5, 5),不是“自动推导”,别误以为有弹性
为什么 s = s[:0] 后 cap 没变,但 s = s[2:] 后 cap 变小了
因为 cap 的定义是“从切片起始指针位置,到底层数组末尾的元素个数”,不是“原始底层数组总长”。指针一动,cap 就跟着重算。
-
s := make([]int, 5, 10)→len=5,cap=10 -
s = s[:0]→ 起始指针没动,只是长度截为 0 →len=0,cap=10 -
s = s[2:]→ 起始指针移到原数组索引 2 处 → 剩余可用空间只剩 8 个元素 →len=?,cap=8(具体取决于截取长度) - 这个行为导致常见坑:多个切片共用底层数组时,一个的
cap缩小,可能让另一个append意外覆盖数据
append 扩容时容量怎么翻倍?有没有例外
Go runtime 的扩容策略不是简单 ×2,而是在小容量时保守增长(避免浪费),大容量时更激进(减少 realloc 次数)。但你不能依赖具体倍数,只能依赖 cap 是否够用。
立即学习“go语言免费学习笔记(深入)”;
- 容量
- ≥1024:每次 ×1.25(向上取整)
- 但无论策略如何,
append后的cap一定 ≥ 新len,且新底层数组内容保证与旧 slice 一致 - 关键提醒:不要用
len(s) == cap(s)判断是否“即将扩容”——截取后len可能等于cap,但底层数组还有富余;反之,满容量切片若被截取,cap也可能突然变大
Cap 和 Len 的区别不在文档里,在指针偏移和底层数组边界的一线计算中。一旦开始做 slice 截取、共享或预分配,光看 len 就会漏掉一半真相。










