切片与底层数组

如何理解下面的代码,数据被追加到原底层数组。如超出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 看不到任何元素。
  • 输出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 个)。
    • s1s 共享底层数组,但限制了长度和容量。
  • 输出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

核心理解

  1. 数据追加到原底层数组
    • 第一次和第二次 append 时,s2 的长度(3 和 4)未超出 s1 的容量 4,数据直接追加到共享的底层数组 [0, 0, 1, 1, ...]
  2. 超出容量限制重新分配
    • 第三次 append 时,长度 5 超出容量 4,Go 为 s2 分配新底层数组,复制数据并追加新元素,s2s1 分离。
  3. 底层数组分离后修改互不影响
    • 分离后,s1s2 各自操作自己的底层数组,修改互不干扰。

输出总结(示例)

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

希望这个逐步分析让你清晰理解了代码的行为!如果还有疑问,随时告诉我。

posted @   仁义礼智信的  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示