繁体   English   中英

Golang 将项目附加到切片

[英]Golang append an item to a slice

为什么切片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]

在您的示例中, Test函数的slice参数接收调用方作用域中变量a的副本

由于切片变量保存“片描述符”,这仅仅是引用了一个潜在的阵列,在您的Test功能,您修改举行的切片描述slice连续可变几次,但是这并不影响调用者和a变量。

Test函数中,第一个append重新分配slice变量下的后备数组,复制其原始内容,向其追加100 ,这就是您所观察到的。 Test退出后, slice变量超出范围,切片引用的(新)底层数组也是如此。 Jeff Lee是正确的,这不是真正发生的事情,所以更新版本如下;正如他正确指出的那样,这个答案是正确的,如果可能有点过于简洁。)

Test函数之外,分配了一个长度为 7 和容量为 8 的切片,并填充了它的 7 个元素。
Test函数中,第一个append看到切片的容量仍然比它的长度大一个元素——换句话说,有空间可以再添加一个元素而无需重新分配。 因此它“吃掉”剩余的元素并将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)

请仔细阅读这篇文章,因为它基本上向您展示了如何在解释切片在内部工作后如何手动实现append 然后读这个

典型的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]

为了使您的代码工作而不必从 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)
}

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

试试这个,我认为这很清楚。 底层数组已更改,但我们的切片未更改, 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]
}

说明(阅读内嵌评论):


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
}

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]
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]

在此处输入图片说明

在此处输入图片说明

更多详情见https://allegro.tech/2017/07/golang-slices-gotcha.html

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

按照 Go Doc 的规定:

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

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

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[:cap(a)]来扩展a ,由Test()函数更改。 如此处指定:

您可以通过重新切片来扩展切片的长度,前提是它有足够的容量。 (链接)

答:append() 将在没有足够的容量时返回新的底层数组。 在你的例子中,

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

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

切片a与切片slice不同,具有不同的len属性。 lencap是 slice 的属性,而不是底层的 array 有关更多详细信息:切片是否按值传递? .

运行下面的例子:

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]

这是一个很好的切片 append 实现。 我想它类似于引擎盖下发生的事情:

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
}

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

// 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
}

是的,当您使用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)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM