在 Go 语言中,len
(长度)和 cap
(容量)是与切片(slice)密切相关的两个概念,尤其是在处理动态数组时。让我详细解释它们的定义、计算方式、关系以及底层的实现。
1. 定义
- 长度(len):表示切片当前包含的元素个数,也就是你实际可以访问的元素数量。
- 容量(cap):表示切片从其第一个元素开始到底层数组末尾的最大可能元素个数,也就是切片当前能扩展到的最大长度(无需重新分配内存)。
简单来说,len
是“已用空间”,cap
是“可用空间”。
2. 如何计算
len
:通过内置函数len(slice)
获取,直接返回切片中当前元素的数量。cap
:通过内置函数cap(slice)
获取,返回从切片的起始位置到底层数组末尾的元素总数。- 切片表达式:当你用
arr[i:j:k]
创建切片时:len = j - i
(切片中实际包含的元素数)。cap = k - i
(从起始位置到指定容量上限的元素数,如果省略k
,则cap = 底层数组长度 - i
)。
例如:
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:3] // 从索引1到3(不包含3)
fmt.Println(len(slice)) // 输出 2 (元素: 2, 3)
fmt.Println(cap(slice)) // 输出 4 (从索引1到数组末尾: 2, 3, 4, 5)
3. 关系
-
len
和cap
的关系:len
始终小于或等于cap
:len(slice) <= cap(slice)
。- 当
len
等于cap
时,切片已用尽其容量,若再追加元素(如用append
),会触发底层数组的重新分配,容量通常会加倍(具体取决于实现和当前容量大小)。 - 通过切片操作,可以改变
len
和cap
,但不能直接修改底层数组的元素个数。
-
动态调整:
- 用
append
添加元素时,如果len < cap
,切片会直接利用剩余容量,len
增加,cap
不变。 - 如果
len == cap
,Go 会分配一个更大的底层数组(通常是当前容量的两倍),将数据复制过去,cap
变为新数组的长度,len
则增加 1。
- 用
例如:
slice := []int{1, 2, 3} // len=3, cap=3
slice = append(slice, 4) // len=4, cap=6(容量翻倍)
4. 底层表示
在 Go 的运行时,切片是一个结构体,定义在 reflect.SliceHeader
中(源码层面),包含三个字段:
- Data:指向底层数组的指针,表示数据的起始位置。
- Len:长度,存储当前切片中的元素个数(int 类型)。
- Cap:容量,存储从 Data 指向的位置到底层数组末尾的元素总数(int 类型)。
底层源码大致如下(伪代码形式):
type SliceHeader struct {Data uintptr // 指向底层数组的指针Len int // 长度Cap int // 容量
}
当你创建一个切片时,例如 slice := arr[1:3]
:
Data
指向arr
的第 1 个元素。Len
设置为 2(从索引 1 到 3 的元素个数)。Cap
设置为 4(从索引 1 到数组末尾的元素个数)。
当使用 append
超出容量时,Go 会:
- 分配一个新的、更大的底层数组。
- 将旧数组的数据复制到新数组。
- 更新
Data
指向新数组,调整Len
和Cap
。
5. 示例与直观理解
arr := [6]int{0, 1, 2, 3, 4, 5}
slice := arr[2:4] // 切片包含 2, 3
fmt.Println(len(slice), cap(slice)) // 输出: 2 4
slice = append(slice, 99) // len=3, cap=4(还有空间)
fmt.Println(len(slice), cap(slice)) // 输出: 3 4
slice = append(slice, 100) // len=4, cap=4(满了)
slice = append(slice, 101) // len=5, cap=8(重新分配,容量翻倍)
fmt.Println(len(slice), cap(slice)) // 输出: 5 8
总结
len
是当前元素数,cap
是潜在最大元素数。len
可变但不超过cap
,cap
由底层数组和起始位置决定。- 底层通过
SliceHeader
结构体管理,动态扩展时会重新分配内存。
希望这个解释清晰明了!如果还有疑问,欢迎继续问我。