切片与底层数组
如何理解下面的代码,数据被追加到原底层数组。如超出cap限制,则为新切片对象重新分配数组。
s := make([]int, 0, 10)
fmt.Printf("s: %v,len: %v,cap: %v\n", s, len(s), cap(s))
s1 := s[:2:4]
fmt.Printf("s1: %v,len: %v,cap: %v\n", s1, len(s1), cap(s1))
s2 := append(s1, 1)
fmt.Printf("s2: %v,len: %v,cap: %v\n", s2, len(s2), cap(s2))
s2 = append(s2, 1)
fmt.Printf("s2: %v,len: %v,cap: %v\n", s2, len(s2), cap(s2))
fmt.Printf("s1底层数组地址: %p: %v\n", &s1[0], s1)
fmt.Printf("s2底层数组地址: %p: %v\n", &s2[0], s2)
s2 = append(s2, 1) // 数据被追加到原底层数组。如超出cap限制,则为新切片对象重新分配数组。
fmt.Printf("s2: %v,len: %v,cap: %v\n", s2, len(s2), cap(s2))
fmt.Printf("s1底层数组地址: %p: %v\n", &s1[0], s1)
fmt.Printf("s2底层数组地址: %p: %v\n", &s2[0], s2)
// s1,s2底层数组变化了,修改互不影响
s2[0] = 1
fmt.Printf("s1: %v,len: %v,cap: %v\n", s1, len(s1), cap(s1))
fmt.Printf("s2: %v,len: %v,cap: %v\n", s2, len(s2), cap(s2))
s1[0] = 2
fmt.Printf("s1: %v,len: %v,cap: %v\n", s1, len(s1), cap(s1))
fmt.Printf("s2: %v,len: %v,cap: %v\n", s2, len(s2), cap(s2))
我来一步步分析这段代码,帮助你理解切片的行为,尤其是数据如何追加到原底层数组,以及超出容量限制时如何重新分配新数组的过程。代码中涉及切片的长度、容量、底层数组共享及分离的机制,我们将通过输出逐步拆解。
初始状态
s := make([]int, 0, 10)
fmt.Printf("s: %v, len: %v, cap: %v\n", s, len(s), cap(s))
s
:使用make
创建一个长度为 0、容量为 10 的切片。- 底层数组分配了 10 个元素的空间,初始值为
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
。 - 但长度为 0,所以
s
看不到任何元素。
- 底层数组分配了 10 个元素的空间,初始值为
- 输出:
s: [], len: 0, cap: 10
切片操作
s1 := s[:2:4]
fmt.Printf("s1: %v, len: %v, cap: %v\n", s1, len(s1), cap(s1))
s1
:通过s[start:end:cap]
切片操作生成。start = 0
:从底层数组第 0 个元素开始。end = 2
:长度为 2(end - start
),包含[0, 0]
。cap = 4
:容量为 4(cap - start
),从第 0 到第 4 个元素(不含第 4 个)。s1
与s
共享底层数组,但限制了长度和容量。
- 输出:
s1: [0 0], len: 2, cap: 4
第一次 append
s2 := append(s1, 1)
fmt.Printf("s2: %v, len: %v, cap: %v\n", s2, len(s2), cap(s2))
s2
:在s1
基础上追加 1。s1
的长度为 2,内容为[0, 0]
,容量为 4。append
将 1 追加到长度 2 的位置(索引 2)。- 新长度 = 3,小于容量 4,数据直接写入共享的底层数组。
- 底层数组变为
[0, 0, 1, 0, ...]
。
s2
:长度 3,容量 4,内容为[0, 0, 1]
。- 输出:
s2: [0 0 1], len: 3, cap: 4
第二次 append
s2 = append(s2, 1)
fmt.Printf("s2: %v, len: %v, cap: %v\n", s2, len(s2), cap(s2))
s2
:在[0, 0, 1]
基础上追加 1。- 当前长度 3,容量 4。
- 新长度 = 4,等于容量 4,仍在容量范围内。
- 数据写入共享的底层数组,变为
[0, 0, 1, 1, ...]
。
s2
:长度 4,容量 4,内容为[0, 0, 1, 1]
。- 输出:
s2: [0 0 1 1], len: 4, cap: 4
fmt.Printf("s1底层数组地址: %p: %v\n", &s1[0], s1)
fmt.Printf("s2底层数组地址: %p: %v\n", &s2[0], s2)
s1
:长度 2,只显示[0, 0]
,但底层数组已变为[0, 0, 1, 1, ...]
。s2
:长度 4,显示[0, 0, 1, 1]
。- 地址:
&s1[0]
和&s2[0]
相同,因为两者仍共享底层数组。 - 输出示例:
s1底层数组地址: 0x...: [0 0]
s2底层数组地址: 0x...: [0 0 1 1]
第三次 append
(超出容量)
s2 = append(s2, 1) // 数据被追加到原底层数组。如超出cap限制,则为新切片对象重新分配数组。
fmt.Printf("s2: %v, len: %v, cap: %v\n", s2, len(s2), cap(s2))
s2
:在[0, 0, 1, 1]
基础上追加 1。- 当前长度 4,容量 4。
- 新长度 = 5,大于容量 4。
- 超出容量:Go 分配一个新底层数组(容量通常翻倍,比如 8),复制
[0, 0, 1, 1]
,追加 1。
s2
:长度 5,容量 8(或更大),内容为[0, 0, 1, 1, 1]
。- 输出:
s2: [0 0 1 1 1], len: 5, cap: 8
fmt.Printf("s1底层数组地址: %p: %v\n", &s1[0], s1)
fmt.Printf("s2底层数组地址: %p: %v\n", &s2[0], s2)
s1
:仍使用原始底层数组,内容为[0, 0]
(长度 2)。s2
:使用新底层数组,内容为[0, 0, 1, 1, 1]
。- 地址:
&s1[0]
和&s2[0]
现在不同。 - 输出示例:
s1底层数组地址: 0x1234: [0 0]
s2底层数组地址: 0x5678: [0 0 1 1 1]
修改操作
s2[0] = 1
fmt.Printf("s1: %v, len: %v, cap: %v\n", s1, len(s1), cap(s1))
fmt.Printf("s2: %v, len: %v, cap: %v\n", s2, len(s2), cap(s2))
s2[0] = 1
:修改s2
的第一个元素。s2
的底层数组变为[1, 0, 1, 1, 1]
。s1
不受影响,因为底层数组已分离。
- 输出:
s1: [0 0], len: 2, cap: 4
s2: [1 0 1 1 1], len: 5, cap: 8
s1[0] = 2
fmt.Printf("s1: %v, len: %v, cap: %v\n", s1, len(s1), cap(s1))
fmt.Printf("s2: %v, len: %v, cap: %v\n", s2, len(s2), cap(s2))
s1[0] = 2
:修改s1
的第一个元素。s1
的底层数组变为[2, 0, 1, 1, ...]
。s2
不受影响,因为底层数组已独立。
- 输出:
s1: [2 0], len: 2, cap: 4
s2: [1 0 1 1 1], len: 5, cap: 8
核心理解
- 数据追加到原底层数组:
- 第一次和第二次
append
时,s2
的长度(3 和 4)未超出s1
的容量 4,数据直接追加到共享的底层数组[0, 0, 1, 1, ...]
。
- 第一次和第二次
- 超出容量限制重新分配:
- 第三次
append
时,长度 5 超出容量 4,Go 为s2
分配新底层数组,复制数据并追加新元素,s2
与s1
分离。
- 第三次
- 底层数组分离后修改互不影响:
- 分离后,
s1
和s2
各自操作自己的底层数组,修改互不干扰。
- 分离后,
输出总结(示例)
s: [], len: 0, cap: 10
s1: [0 0], len: 2, cap: 4
s2: [0 0 1], len: 3, cap: 4
s2: [0 0 1 1], len: 4, cap: 4
s1底层数组地址: 0x1234: [0 0]
s2底层数组地址: 0x1234: [0 0 1 1]
s2: [0 0 1 1 1], len: 5, cap: 8
s1底层数组地址: 0x1234: [0 0]
s2底层数组地址: 0x5678: [0 0 1 1 1]
s1: [0 0], len: 2, cap: 4
s2: [1 0 1 1 1], len: 5, cap: 8
s1: [2 0], len: 2, cap: 4
s2: [1 0 1 1 1], len: 5, cap: 8
希望这个逐步分析让你清晰理解了代码的行为!如果还有疑问,随时告诉我。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!