Golang将一个项目追加到一个切片
为什么一直保持不变? 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) }
在你的例子中, Test
函数的slice
参数在调用者的作用域中接收variablesa
一个副本 。
由于一个切片variables包含一个“切片描述符”,它只引用一个底层数组,所以在你的Test
函数中,你可以连续多次修改slice
variables中的切片描述符,但是这不影响调用者及其variables。
在Test
函数内部,第一个append
重新分配slice
variables下的backing数组,将其原始内容复制过来,并附加100
,这就是你正在观察的内容。 从Test
退出时, slice
variables超出了范围,并且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
可以在原地修改它的参数, 或者根据其input的大小和容量,用一个附加的条目返回它的参数的一个副本。 使用以前附加的切片可能会给出意想不到的结果,例如
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]
注意如果cap不够用, append会生成一个新的slice。 @ kostix的回答是正确的,或者你可以通过指针传递slice参数!
试试这个,我觉得很清楚。 底层数组被改变,但我们的切片不是, 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] }
为了使你的代码无需从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) }
Go采取更精简和懒惰的方法来做到这一点。 它不断修改相同的底层数组,直到达到一个分片的容量。
Ref: http : //criticalindirection.com/2016/02/17/slice-with-a-pinch-of-salt/
来自链接的示例输出解释了Go中切片的行为。
创build切片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也修改一个,因为它们指向相同的基础数组。
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。 覆盖一个。
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。 覆盖一个。
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]
validation切片a和b在上一步中的容量超载之后指向不同的基础arrays。
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 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 }
我认为原来的答案不完全正确。 append()
改变了切片和底层数组,即使底层数组已经改变,但仍然由两个切片共享。
按照Go Doc的规定:
切片不存储任何数据,只是描述了底层数组的一部分。 (链接)
切片只是数组周围的包装值,这意味着它们包含有关如何切片存储一组数据的底层数组的信息。 因此,默认情况下,切片在传递给另一个方法时,实际上是通过值传递的,而不是引用/指针,即使它们仍然使用相同的基础数组。 通常情况下,数组也是通过值传递,所以我假设一个切片指向一个底层数组,而不是将其存储为一个值。 关于你的问题,当你运行时把你的slice传递给下面的函数:
func Test(slice []int) { slice = append(slice, 100) fmt.Println(slice) }
你实际上传递了一个slice的副本和一个指向同一个底层数组的指针。这意味着,你对这个slice
所做的改变并不影响main
函数中的改变。 这个片本身就存储了它所切片的数组的多less信息,并向公众公开。 因此,在扩展底层数组的同时,当您运行append(slice, 1000)
时,也更改了切片的切片信息,切片信息在Test()
函数中保持为私有。
但是,如果您已经按照以下方式更改了代码,则可能发挥了作用:
func main() { for i := 0; i < 7; i++ { a[i] = i } Test(a) fmt.Println(a[:cap(a)]) }
原因是你通过在其改变的底层数组上面说a[:cap(a)]
来扩展它,被Test()
函数改变。 如下所述:
你可以通过重新切片来延长切片的长度,只要它有足够的容量。 (链接)