前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang-101-hacks(14)——切片与数组的关联

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

作者头像
羊羽shine
发布2019-06-14 10:17:22
4630
发布2019-06-14 10:17:22
举报
文章被收录于专栏:Golang开发Golang开发

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

代码语言:javascript
复制
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加到切片的尾部,我们期望的结果是:

代码语言:javascript
复制
[[0 0 1] [0 0 2]]
[[0 0 0 1] [0 0 0 2]]

但是实际的输出结果是:

代码语言:javascript
复制
[[0 0 1] [0 0 2]]
[[0 0 0 2] [0 0 0 2]]

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

代码语言:javascript
复制
(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))”后发现:

代码语言:javascript
复制
(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:

代码语言:javascript
复制
(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 看看到底发生了什么:

代码语言:javascript
复制
(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的值:

代码语言:javascript
复制
(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:

代码语言:javascript
复制
(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!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.06.08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档