Golang将一个项目附加到切片上

时间:2013-11-25 14:14:49

标签: go

为什么切片a保持不变? append()生成新切片吗?

package main

import (
    "fmt"
)

var a = make([]int, 7, 8)

func Test(slice []int) {
    slice = append(slice, 100)
    fmt.Println(slice)
}

func main() {
    for i := 0; i < 7; i++ {
        a[i] = i
    }

    Test(a)
    fmt.Println(a)
}

输出:

[0 1 2 3 4 5 6 100]
[0 1 2 3 4 5 6]

13 个答案:

答案 0 :(得分:44)

在您的示例中,slice函数的Test参数在调用者范围内接收变量a副本

由于切片变量包含仅仅引用基础数组的“切片描述符”,因此在Test函数中修改了slice变量中保存的切片描述符连续几次,但这不会影响调用者及其a变量。

Test函数内,第一个append重新分配slice变量下的支持数组,复制其原始内容,将100附加到其中,这就是你在观察。退出Test后,slice变量超出范围,切片引用的(新)基础数组也超出范围。

如果你想让Test表现得像append你必须从中返回新的 - 就像append那样 - 并要求Test的来电者使用它的方式与使用append的方式相同:

func Test(slice []int) []int {
    slice = append(slice, 100)

    fmt.Println(slice)

    return slice
}

a = Test(a)

请仔细阅读this article,因为它在解释切片内部工作原理后,基本上向您展示了如何手动实施append。然后阅读this

答案 1 :(得分:23)

典型的append用法是

a = append(a, x)

因为append可以就地修改其参数返回其参数的副本,并附加一个条目,具体取决于其输入的大小和容量。使用先前附加的切片可能会产生意外结果,例如

a := []int{1,2,3}
a = append(a, 4)
fmt.Println(a)
append(a[:3], 5)
fmt.Println(a)

可以打印

[1 2 3 4]
[1 2 3 5]

答案 2 :(得分:5)

如果 cap 不足,请注意追加会生成新切片。 @ kostix的答案是正确的,或者您可以通过指针传递 slice 参数!

答案 3 :(得分:5)

试试这个,我认为这一点很清楚。基础数组已更改,但我们的切片不是,print只打印len()个字符,另一个切片到cap(),您可以看到更改的数组:

func main() {

  for i := 0; i < 7; i++ {
      a[i] = i
  }

  Test(a)

  fmt.Println(a) // prints [0..6]
  fmt.Println(a[:cap(a)] // prints [0..6,100]
}

答案 4 :(得分:2)

  

Go采取更精益和懒惰的方法来做到这一点。它使   修改相同的底层数组,直到切片的容量为止   达到。

参考:http://criticalindirection.com/2016/02/17/slice-with-a-pinch-of-salt/

链接中示例的输出解释了Go中切片的行为。

创建切片a。

Slice a len=7 cap=7 [0 0 0 0 0 0 0]

切片b指的是切片a中的2,3,4个索引。因此,容量为5(= 7-2)。

b := a[2:5]
Slice b len=3 cap=5 [0 0 0]

修改切片b,也会修改a,因为它们指向相同的基础数组。

b[0] = 9
Slice a len=7 cap=7 [0 0 9 0 0 0 0]
Slice b len=3 cap=5 [9 0 0]

将1添加到切片b。覆盖a。

Slice a len=7 cap=7 [0 0 9 0 0 1 0]
Slice b len=4 cap=5 [9 0 0 1]

将2添加到切片b。覆盖a。

Slice a len=7 cap=7 [0 0 9 0 0 1 2]
Slice b len=5 cap=5 [9 0 0 1 2]

将3添加到切片b。此处,在容量过载时会生成新副本。

Slice a len=7 cap=7 [0 0 9 0 0 1 2]
Slice b len=6 cap=12 [9 0 0 1 2 3]

在上一步中的容量过载后,验证切片a和b指向不同的基础数组。

b[1] = 8
Slice a len=7 cap=7 [0 0 9 0 0 1 2]
Slice b len=6 cap=12 [9 8 0 1 2 3]

答案 5 :(得分:2)

说明(阅读内联注释):


package main

import (
    "fmt"
)

var a = make([]int, 7, 8)
// A slice is a descriptor of an array segment. 
// It consists of a pointer to the array, the length of the segment, and its capacity (the maximum length of the segment).
// The length is the number of elements referred to by the slice.
// The capacity is the number of elements in the underlying array (beginning at the element referred to by the slice pointer).
// |-> Refer to: https://blog.golang.org/go-slices-usage-and-internals -> "Slice internals" section

func Test(slice []int) {
    // slice receives a copy of slice `a` which point to the same array as slice `a`
    slice[6] = 10
    slice = append(slice, 100)
    // since `slice` capacity is 8 & length is 7, it can add 100 and make the length 8
    fmt.Println(slice, len(slice), cap(slice), " << Test 1")
    slice = append(slice, 200)
    // since `slice` capacity is 8 & length also 8, slice has to make a new slice 
    // - with double of size with point to new array (see Reference 1 below).
    // (I'm also confused, why not (n+1)*2=20). But make a new slice of 16 capacity).
    slice[6] = 13 // make sure, it's a new slice :)
    fmt.Println(slice, len(slice), cap(slice), " << Test 2")
}

func main() {
    for i := 0; i < 7; i++ {
        a[i] = i
    }

    fmt.Println(a, len(a), cap(a))
    Test(a)
    fmt.Println(a, len(a), cap(a))
    fmt.Println(a[:cap(a)], len(a), cap(a))
    // fmt.Println(a[:cap(a)+1], len(a), cap(a)) -> this'll not work
}

输出:

[0 1 2 3 4 5 6] 7 8
[0 1 2 3 4 5 10 100] 8 8  << Test 1
[0 1 2 3 4 5 13 100 200] 9 16  << Test 2
[0 1 2 3 4 5 10] 7 8
[0 1 2 3 4 5 10 100] 7 8

参考文献1:https://blog.golang.org/go-slices-usage-and-internals

func AppendByte(slice []byte, data ...byte) []byte {
    m := len(slice)
    n := m + len(data)
    if n > cap(slice) { // if necessary, reallocate
        // allocate double what's needed, for future growth.
        newSlice := make([]byte, (n+1)*2)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:n]
    copy(slice[m:n], data)
    return slice
}

答案 6 :(得分:1)

为了使您的代码无需从Test返回切片,您可以传递这样的指针:

package main

import (
    "fmt"
)

var a = make([]int, 7, 8)

func Test(slice *[]int) {
    *slice = append(*slice, 100)

    fmt.Println(*slice)
}

func main() {

    for i := 0; i < 7; i++ {
        a[i] = i
    }

    Test(&a)

    fmt.Println(a)
}

答案 7 :(得分:1)

我认为原来的答案并不完全正确。 append()更改了切片和基础数组,即使基础数组已更改但仍由两个切片共享。

由Go Doc指定:

  

切片不存储任何数据,它只描述底层数组的一部分。 (Link)

切片只是数组的包装值,这意味着它们包含有关如何切割用于存储一组数据的基础数组的信息。因此,默认情况下,传递给另一个方法时,切片实际上是通过值传递的,而不是引用/指针,即使它们仍然使用相同的底层数组。通常,数组也通过值传递,因此我假设切片指向底层数组而不是将其存储为值。关于你的问题,当你运行切片到以下函数时:

func Test(slice []int) {
    slice = append(slice, 100)
    fmt.Println(slice)
}

您实际上传递了切片的副本以及指向同一基础数组的指针。这意味着,您对slice所做的更改不会影响main函数中的更改。切片本身存储有关切片和暴露给公众的阵列数量的信息。因此,当您在展开基础数组时运行append(slice, 1000)时,您还更改了slice的切片信息,这些信息在Test()函数中保密。

但是,如果您按如下方式更改了代码,则可能有效:

func main() {
    for i := 0; i < 7; i++ {
        a[i] = i
    }

    Test(a)
    fmt.Println(a[:cap(a)])
}

原因是您通过a对其已更改的基础数组a[:cap(a)]进行了扩展,并由Test()函数更改。如此处所述:

  

只要具有足够的容量,您可以通过重新切片来延长切片的长度。 (Link)

答案 8 :(得分:1)

package main

import (
    "fmt"
)

func a() {
    x := []int{}
    x = append(x, 0)
    x = append(x, 1)  // commonTags := labelsToTags(app.Labels)
    y := append(x, 2) // Tags: append(commonTags, labelsToTags(d.Labels)...)
    z := append(x, 3) // Tags: append(commonTags, labelsToTags(d.Labels)...)
    fmt.Println(y, z)
}

func b() {
    x := []int{}
    x = append(x, 0)
    x = append(x, 1)
    x = append(x, 2)  // commonTags := labelsToTags(app.Labels)
    y := append(x, 3) // Tags: append(commonTags, labelsToTags(d.Labels)...)
    z := append(x, 4) // Tags: append(commonTags, labelsToTags(d.Labels)...)
    fmt.Println(y, z)
}

func main() {
    a()
    b()
}

First guess could be

[0, 1, 2] [0, 1, 3]
[0, 1, 2, 3] [0, 1, 2, 4]

but in fact it results in

[0, 1, 2] [0, 1, 3]
[0, 1, 2, 4] [0, 1, 2, 4]

enter image description here

enter image description here

更多详细信息,请参见https://allegro.tech/2017/07/golang-slices-gotcha.html

答案 9 :(得分:1)

答案:如果没有足够的容量,append() 将返回新的底层数组。在您的示例中,

var a = make([]int, 7, 8)

您将底层数组(容量为 8)的切片(长度为 7)分配给 a,然后将其作为参数 slice 传递给函数。当调用 append() 时,它找到了 1 容量,然后只需将 slicelen 从 7 更新为 8,并将值 100 放入该位置。

切片 a 与切片 slice 不同,具有不同的 len 属性。 lencap 是 slice 的属性,而不是底层数组。更多详情:Are slices passed by value?

运行下面的例子:

package main

import (
    "fmt"
)

var a = make([]int, 7, 8)

func Test(slice []int) {
    fmt.Printf("slice's address is %p\n", &slice)
    fmt.Println("slice: cap=",cap(slice),"len=",len(slice))
    slice = append(slice, 100)
    fmt.Println("slice: cap=",cap(slice),"len=",len(slice))
    fmt.Println(slice)
}

func main() {
    for i := 0; i < 7; i++ {
        a[i] = i
    }
    fmt.Printf("a's address is %p\n", &a)
    fmt.Println("a: cap=",cap(a),"len=",len(a))
    Test(a)
    fmt.Println("a: cap=",cap(a),"len=",len(a))
    fmt.Println(a)
    fmt.Println(a[:8]) // manully extend a's len to cap of 8
}

结果是:

❯❯  Temp  17:33  go run .\test.go
a's address is 0x2cbfc0
a: cap= 8 len= 7
slice's address is 0xc000098060
slice: cap= 8 len= 7
slice: cap= 8 len= 8
[0 1 2 3 4 5 6 100]
a: cap= 8 len= 7
[0 1 2 3 4 5 6]
[0 1 2 3 4 5 6 100]

答案 10 :(得分:0)

这是一个很好的附加切片实现。我猜它与幕后发生的情况类似:

package main

import "fmt"

func main() {
    slice1 := []int{0, 1, 2, 3, 4}
    slice2 := []int{55, 66, 77}
    fmt.Println(slice1)
    slice1 = Append(slice1, slice2...) // The '...' is essential!
    fmt.Println(slice1)
}

// Append ...
func Append(slice []int, items ...int) []int {
    for _, item := range items {
        slice = Extend(slice, item)
    }
    return slice
}

// Extend ...
func Extend(slice []int, element int) []int {
    n := len(slice)
    if n == cap(slice) {
        // Slice is full; must grow.
        // We double its size and add 1, so if the size is zero we still grow.
        newSlice := make([]int, len(slice), 2*len(slice)+1)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0 : n+1]
    slice[n] = element
    return slice
}

答案 11 :(得分:0)

附加到切片的末尾,如果切片为空则创建一个新条目

// in := [][]int{{}}, in := [][]int{{1,3},{2,3}}
// addtoEndofSliceArray(in,10)
// out=[[10]], out=[[1,3],[2,3,10]]

func addtoEndofSliceArray(in [][]int,element int)(out [][]int){
    if len(in) >0 {
        k :=in[len(in)-1]
        k = append(k,element)
        in = in[:len(in)-1]
        in = append(in, k)
    }else{
        in = [][]int{{element}}
    }
    return in
}

答案 12 :(得分:0)

是的,当您使用 append() 向 Slice 添加值时,它通常会创建一个新 Slice 而不会覆盖原始 Slice。

检查下面的代码片段

package main

import "fmt"

func main() {

    var ages = []int{3,6,8,1,9}

    fmt.Println(append(ages, 13))

    fmt.Println("Original Slice is still: ", ages)
}

如果您需要覆盖原来的 Slice,您需要将 append() 设置为 Slice 名称,如下所示。

ages = append(ages, 12)
fmt.Println("Now original Slice is: ", ages)