cap(s)返回从切片起始指针到底层数组末尾的元素总数,len(s)返回当前可安全访问的元素个数;二者语义严格分离,仅适用于切片和通道,对数组、字符串、map调用会编译报错。

cap 和 len 不是“差不多的概念”,它们在 Go 里指向完全不同的内存事实:一个管“现在能用多少”,一个管“从这儿开始还能塞多少”——错混使用会直接导致内存意外共享、扩容误判或 panic。
切片里 len(s) 和 cap(s) 到底返回什么
二者都是对切片头结构体字段的直接读取,零开销,但语义隔离极强:
-
len(s)是当前可安全读写的元素个数,索引范围是0 ;越界访问(比如 <code>s[len(s)])会 panic -
cap(s)是从s的起始指针位置开始、到底层数组末尾的元素总数,它不关心前面有没有被截掉的部分 - 同一个底层数组,不同切片的
cap可以完全不同:比如s := arr[1:3],cap(s)是 4(不是 5),因为起始偏移了 1 位 -
cap(s)不是“预留空间”,也不是“总数组长度”,它是运行时快照,随切片起始地址移动而实时重算
哪些类型支持 cap()?哪些调用会编译报错
Go 对 cap 的支持非常严格,不是所有集合类型都能用:
- ✅ 支持:
[]T(切片)、chan T(带缓冲通道) - ❌ 不支持:
string(调用cap("abc")→invalid argument to cap)、map[K]V(无容量概念)、[N]T(数组的cap恒等于len,但极少需要显式查) - ⚠️ 注意:
len(ch)返回当前缓存中未读元素数,cap(ch)才是创建时指定的缓冲大小;两者单位一致但含义不同
make([]T, len, cap) 三个参数怎么配才不翻车
第三个参数不是“建议值”,而是直接决定底层数组分配大小和起始偏移,配错就埋下共享或频繁 realloc 的隐患:
-
make([]byte, 0, 4096):适合接收流式数据(如 HTTP body),首次append不扩容 -
make([]int, 5, 5):刚创建就满载,下一次append必触发拷贝——这不是 bug,是明确设计 -
make([]byte, 5, 1024):已有 5 字节有效数据,还能追加 1019 字节不换底层数组 - 省略第三个参数(如
make([]int, 5))等价于make([]int, 5, 5),不是“自动推导”,别指望它聪明 - 用
s[i:j:k]截取可显式限制新切片的cap,比如s[2:4:4]的cap就是 2,避免后续append覆盖原数组后段
为什么 cap(s) - len(s) 不是安全的“剩余空间”判断依据
这个差值看起来像可用余量,但实际极易误导,尤其在多次截取或跨函数传递后:
- 截取操作(如
s = s[2:])会让cap缩小,但底层数组没变——此时cap - len变小,不代表真实内存紧张 - 反过来,
s = s[:0]后cap不变,cap - len看似很大,但若原切片来自大数组中间段,这块“空闲”区域可能已被其他切片持有并修改 - 真正可靠的扩容判断是:
if len(s)+n > cap(s),而不是if cap(s)-len(s) (后者在负数或溢出时更危险) - 想彻底切断底层数组关联,唯一可靠方式是
append([]T(nil), s...),强制复制
最易被忽略的点:cap 的变化永远绑定起始指针位置,不是底层数组本身。这意味着你无法仅靠 len 和 cap 数值判断两个切片是否共享内存——得看 &s[0] 地址是否相同,或者用 unsafe.Slice 比对区间重叠。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











