簡體   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