golang-101-hacks(14)——切片与数组的关联

羊羽share · · 618 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

注:本文是对golang-101-hacks中文翻译。
往切片中增加数时,如果切片的所关联的数组没有足够的空间,会重新开辟一个新的数组空间。同时将原先数组中的元素复制到这个新数组对应的内存中,将新添加数据加到数组尾部。因此,在使用Go内置的append函数时,需要小心谨慎,始终牢记“数组可能已经更改”的思想!
下面刻意营造的的例子来说明我们需要注意的点:

package main

import (
    "fmt"
)

func addTail(s []int)  {
    var ns [][]int
    for _, v := range []int{1, 2} {
        ns = append(ns, append(s, v))
    }
    fmt.Println(ns)
}

func main() {
    s1 := []int{0, 0}
    s2 := append(s1, 0)
    for _, v := range [][]int{s1, s2} {
        addTail(v)
    }
}   

s1是[0,0] s2是[0,0,0],执行addTail函数分别将1和2加到切片的尾部,我们期望的结果是:

[[0 0 1] [0 0 2]]
[[0 0 0 1] [0 0 0 2]]

但是实际的输出结果是:

[[0 0 1] [0 0 2]]
[[0 0 0 2] [0 0 0 2]]

运行结果s1的与期望结果一致,但是s2却不是
让我们用delve调试这个问题,检查slice的内部机制 addTail函数设置断点,查看s1时第一执行:

(dlv) n
> main.addTail() ./slice.go:8 (PC: 0x401022)
     3: import (
     4:         "fmt"
     5: )
     6:
     7: func addTail(s []int)  {
=>   8:         var ns [][]int
     9:         for _, v := range []int{1, 2} {
    10:                 ns = append(ns, append(s, v))
    11:         }
    12:         fmt.Println(ns)
    13: }
(dlv) p s
[]int len: 2, cap: 2, [0,0]
(dlv) p &s[0]
(*int)(0xc82000a2a0)

s1的长度和容量都是2,执行的底层数组地址是0xc82000a2a0,当执行ns = append(ns, append(s, v))时由于s1的长度和容量都是2,已经没有空间给存储新的值了。要增加一个新值,必须创建一个新数组,它包含s1中的[0,0]和新值(1或2)。执行“ns = append(ns, append(s, v))”后发现:

(dlv) n
> main.addTail() ./slice.go:9 (PC: 0x401217)
     4:         "fmt"
     5: )
     6:
     7: func addTail(s []int)  {
     8:         var ns [][]int
=>   9:         for _, v := range []int{1, 2} {
    10:                 ns = append(ns, append(s, v))
    11:         }
    12:         fmt.Println(ns)
    13: }
    14:
(dlv) p ns
[][]int len: 1, cap: 1, [
        [0,0,1],
]
(dlv) p ns[0]
[]int len: 3, cap: 4, [0,0,1]
(dlv) p &ns[0][0]
(*int)(0xc82000e240)
(dlv) p s
[]int len: 2, cap: 2, [0,0]
(dlv) p &s[0]
(*int)(0xc82000a2a0)

可以看到,新切片的长度为3,容量为4,底层数组地址为0xc82000e240,与s1 (0xc82000a2a0)不同。继续执行,直到退出循环:
We can see the length of anonymous slice is 3, capacity is 4, and the underlying array address is 0xc82000e240, different from s1's (0xc82000a2a0). Continue executing until exit loop:

(dlv) n
> main.addTail() ./slice.go:12 (PC: 0x40124c)
     7: func addTail(s []int)  {
     8:         var ns [][]int
     9:         for _, v := range []int{1, 2} {
    10:                 ns = append(ns, append(s, v))
    11:         }
=>  12:         fmt.Println(ns)
    13: }
    14:
    15: func main() {
    16:         s1 := []int{0, 0}
    17:         s2 := append(s1, 0)
(dlv) p ns
[][]int len: 2, cap: 2, [
        [0,0,1],
        [0,0,2],
]
(dlv) p &ns[0][0]
(*int)(0xc82000e240)
(dlv) p &ns[1][0]
(*int)(0xc82000e280)
(dlv) p &s[0]
(*int)(0xc82000a2a0)

我们可以看到s1 n[0] n[1]分别指向有3个不同的数组。
现在,让我们按照相同的步骤查看s2 看看到底发生了什么:

(dlv) n
> main.addTail() ./slice.go:8 (PC: 0x401022)
     3: import (
     4:         "fmt"
     5: )
     6:
     7: func addTail(s []int)  {
=>   8:         var ns [][]int
     9:         for _, v := range []int{1, 2} {
    10:                 ns = append(ns, append(s, v))
    11:         }
    12:         fmt.Println(ns)
    13: }
(dlv) p s
[]int len: 3, cap: 4, [0,0,0]
(dlv) p &s[0]
(*int)(0xc82000e220)

s2的长度是3,容量是4,所以有一个空间可以添加新元素。第一次执行“ns = append(ns, append(s, v))”后查看s2和ns的值:

(dlv)
> main.addTail() ./slice.go:9 (PC: 0x401217)
     4:         "fmt"
     5: )
     6:
     7: func addTail(s []int)  {
     8:         var ns [][]int
=>   9:         for _, v := range []int{1, 2} {
    10:                 ns = append(ns, append(s, v))
    11:         }
    12:         fmt.Println(ns)
    13: }
    14:
(dlv) p ns
[][]int len: 1, cap: 1, [
        [0,0,0,1],
]
(dlv) p &ns[0][0]
(*int)(0xc82000e220)
(dlv) p s
[]int len: 3, cap: 4, [0,0,0]
(dlv) p &s[0]
(*int)(0xc82000e220)

我们可以看到新切片的数组地址也是0xc82000e220,这是因为s2有足够的空间容纳新元素,不需要分配新的数组。添加2后再次查看s2和ns:

(dlv)
> main.addTail() ./slice.go:12 (PC: 0x40124c)
     7: func addTail(s []int)  {
     8:         var ns [][]int
     9:         for _, v := range []int{1, 2} {
    10:                 ns = append(ns, append(s, v))
    11:         }
=>  12:         fmt.Println(ns)
    13: }
    14:
    15: func main() {
    16:         s1 := []int{0, 0}
    17:         s2 := append(s1, 0)
(dlv) p ns
[][]int len: 2, cap: 2, [
        [0,0,0,2],
        [0,0,0,2],
]
(dlv) p &ns[0][0]
(*int)(0xc82000e220)
(dlv) p &ns[1][0]
(*int)(0xc82000e220)
(dlv) p s
[]int len: 3, cap: 4, [0,0,0]
(dlv) p &s[0]
(*int)(0xc82000e220)

3个切片都指向同一个数组,因此后面的值(2)将覆盖前面的项(1)。
总之,append函数处理起来非常棘手,因为它可以在您毫不知情下修改底层数组。必须清楚地了解每个切片底层数组的内存分配,否则切片可能会给您带来一个大大的surprise!


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:羊羽share

查看原文:golang-101-hacks(14)——切片与数组的关联

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

618 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传