使用引用或复制指向切片的指针?

时间:2014-12-27 08:38:35

标签: go

我发现我的代码有不同的结果如下,这是一个指针,指的是Go.Tour编译器(http://tour.golang.org/welcome/1)和我的本地编译器(Go 1.4版)之间的幻灯片

哪一个是正确的?我也想知道我的代码p1,p2之间的指针是如何工作的? 因为地址似乎没有移动,但p1使用引用但p2使用copy。

package main

import "fmt"

func main() {
    var a []int
    var b []int
    a = append(a, 0)
    b = append(b, 0)
    p := &a[0]
    fmt.Printf("a[0] = %d pointer=%d, p = %d \n", a[0], &a[0], *p)
    a[0] = 2
    fmt.Printf("a[0] = %d pointer=%d, p = %d \n", a[0], &a[0], *p)
    /*
        a[0] = 0, p = 0
        a[0] = 2, p = 2
    */
    var c []int
    var d []int
    c = append(c, 0)
    d = append(d, 0)
    p2 := &c[0]
    fmt.Printf("c[0]=%d pointer=%d, p2 = %d\n", c[0], &c[0], *p2)
    c = append(c, 1)
    c[0] = 2
    fmt.Printf("c[0]=%d pointer=%d, p2 = %d\n", c[0], &c[0], *p2)
    /* 
        c[0]=0, p2 = 0
        c[0]=2, p2 = 0

      copy the same code run in http://tour.golang.org/welcome/1 will get.
        c[0]=0, p2 = 0
        c[0]=2, p2 = *2*  << why??

    */
}

更新: 我使用指针切片的原因是我试图测试向量push_pack问题,它在Go的Web端存在。请参阅http://doc.rust-lang.org/nightly/intro.html#ownership

2 个答案:

答案 0 :(得分:5)

首先,对于切片的任何内容,我建议您阅读Go Slices: usage and internals。简短的故事是Go对切片容量的处理可能会让人感到不安。

给定的切片变量有三个组件:指向数据数组的基础指针,长度和容量。关于差异的大量词汇已经泄露,但这里的重要部分是长度(有效地)是底层内存缓冲区的当前使用部分,而容量是底层缓冲区的总体大小。这是一个不精确的定义,但它在实践中运作良好。

神秘的下一部分是append内置。有时候附加功能实际上有点难以理解,可能是Go中最大的陷阱之一:

  1. 如果底层缓冲区足够大(cap&gt; len),只需按要添加的元素数量增加len,并将数据放入新空间。
  2. 如果底层缓冲区不够大,请分配一个容量较大的新缓冲区,将旧缓冲区复制到新缓冲区中,然后添加所有新元素。
  3. 2的最大难点在于在追加后在同一切片上进行两次任意操作,很难知道旧的或新的内存缓冲区是否是先验的。的确,让我们试试这个:

    var c []int
    var d []int
    c = append(c, 0)
    d = append(d, 0)
    p2 := &c[0]
    fmt.Printf("c[0]=%d pointer=%d, p2 = %d\n", c[0], &c[0], *p2)
    c = append(c, 1)
    c[0] = 2
    fmt.Printf("c[0]=%d pointer=%d, p2 = %d\n", c[0], &c[0], *p2)
    c = append(c, 1)
    c[0] = 25
    fmt.Printf("c[0]=%d pointer=%d, p2 = %d\n", c[0], &c[0], *p2)
    

    playground

    你会得到c[0]=25, p2=2。我们只添加了一个语句,突然指针和切片值发散了!

    这意味着更改了上限,或者更确切地说,使用了新的缓冲区。实际上,在第一次追加后,但在第三次追加之前打印cap(c)将产生2。这意味着当将单个元素附加到容量0的切片时,Go会将[脚注]初始化为长度为1且容量 2 的切片。因此,在第二次追加后没有分配新的缓冲区,因为它有空间。这就是为什么p2c[0]在第二次附加后相同而不是第三次附加的原因。

    一般而言,虽然切片和对内存中特定位置的引用的确切规则是一致的,但实际上切片增长行为非常精巧,通常最好不要依赖指针切片值(或者两个切片变量具有相同的底层缓冲区),除非您计划从不使用追加,或者使用make预先分配缓冲区,使用append永远不会重新分配。

    [脚注]不完全正确,我想发出一个巨大的警告附加后的确切容量依赖于实现。请不要依赖附件在编译器或甚至不同编译器目标之间的相容能力

答案 1 :(得分:1)

要在两个环境(play.golang.org和local go 1.4)中获得相同的结果,您需要添加:

c[0] = 2
p2 = &c[0] // REDEFINE p2 there

(如this example

当在本地运行时,这将给出:

c[0]=0 pointer=826814759400, p2 = 0
c[0]=2 pointer=826814759440, p2 = 2

而不是:

c[0]=0 pointer=826814759400, p2 = 0
c[0]=2 pointer=826814759440, p2 = 0

正如“Inside the Go Playground”中所提到的那样,由于“Playground程序的CPU时间和内存可以使用的数量有限”,因此切片的内存分配在操场之间的管理方式不同和完整的Go计划。

如果您要删除c = append(c, 1),您将获得预期的结果。

使用本地程序,扩展切片results in a new slice:其默认容量为1,添加新元素会创建一个具有新容量的新切片(请参阅“slice internals”)。 不适用于默认情况下切片容量可能较大的操场。