[英]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
,这就是您所观察到的。从 ( Jeff Lee是正确的,这不是真正发生的事情,所以更新版本如下;正如他正确指出的那样,这个答案是正确的,如果可能有点过于简洁。)Test
退出后, slice
变量超出范围,切片引用的(新)底层数组也是如此。
在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
用法是
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]
我认为原始答案并不完全正确。 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
容量,然后只需将slice
的len
从 7 更新到 8 并将值 100 放入该位置。
切片a
与切片slice
不同,具有不同的len
属性。 len
和cap
是 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.