在阅读Go切片时,我在append
方法的上下文中遇到了此行为
如果s的后备数组太小而无法容纳所有给定值,则会分配一个更大的数组。返回的切片将指向新分配的数组。
源-Golang Tour
要理解这一点,我编写了以下代码:
func makeSlices() {
var a []int;
a = append(a, 0)
b := append(a, 1)
printSlice("b", b)
c := append(a, 2)
printSlice("b", b)
printSlice("c", c)
}
func printSlice(name string, s []int) {
fmt.Printf("var=%v len=%d cap=%d first_address=%v %v\n", name, len(s), cap(s), &s[0], s)
}
输出:
var=b len=2 cap=2 first_address=0x414020 [0 1]
var=b len=2 cap=2 first_address=0x414020 [0 2]
var=c len=2 cap=2 first_address=0x414020 [0 2]
我希望b
和c
指向相同的基础数组,因为它们都是相同长度的切片
但是如果我将相同的代码用于另一片切片:
func makeSlices() {
var a []int;
a = append(a, 0, 9)
d := append(a, 1, 2)
printSlice("d", d)
e := append(a, 3, 4)
printSlice("d", d)
printSlice("e", e)
}
输出:
var=d len=5 cap=8 first_address=0x450020 [0 0 9 1 2]
var=d len=5 cap=8 first_address=0x450020 [0 0 9 1 2]
var=e len=5 cap=8 first_address=0x450040 [0 0 9 3 4]
在这种情况下,d
和e
应该指向相同的支持数组,因为它们又是相同长度的切片,但它们并非相同。
为什么这种行为异常? Go何时确切决定将新的支持数组分配给切片?
答案 0 :(得分:4)
答案很简单:如果要附加的元素不适合当前容量,则append()
分配新的后备数组(并复制当前内容)。正式:
if len(s) + len(newElements) > cap(s) {
// Allocate new backing array
// copy content (s) over to new array
} else {
// Just resize existing slice
}
// append (copy) newElements
例如,如果len = 2,cap = 4,则可以追加2个元素,无需分配。
如果len = 2,cap = 4,并追加3个元素,则len + 3> cap,因此将分配一个新的后备阵列(其容量将大于len + 3,考虑到未来的增长,但是其长度将为2 + 3 = 5)。
在第一个示例中,您声明了一个切片变量,其长度和容量为0。
var a []int
fmt.Println(len(a), cap(a)) // Prints 0 0
第一次添加时,将分配一个新数组:
a = append(a, 0)
fmt.Println(len(a), cap(a)) // Prints 1 2
当您执行另一个追加时,它会适合容量,因此不会分配:
fmt.Println(len(a), cap(a)) // Prints 1 2
b := append(a, 1)
fmt.Println(len(b), cap(b)) // Prints 2 2
但是这次您将结果切片存储在b
中,而不是存储在a
中。因此,如果对a
进行第3次追加,则仍然具有length = 1和cap = 2,因此向a
追加另一个元素将不需要分配:
fmt.Println(len(a), cap(a)) // Prints 1 2
c := append(a, 2)
fmt.Println(len(c), cap(c)) // Prints 2 2
因此,除了第一个附录外,所有其他附录都不需要分配,因此,第一个分配的后备数组用于所有a
,b
和c
切片,因此地址为他们的第一个元素将是相同的。这就是您所看到的。
再次创建一个空切片(len = 0,cap = 0)。
然后您进行第一次附加:2个元素:
a = append(a, 0, 9)
fmt.Println(len(a), cap(a)) // Prints 2 2
这将分配一个新的数组,其长度为2,因此切片的长度和容量均为2。
然后您进行第二次追加:
d := append(a, 1, 2)
fmt.Println(len(d), cap(d)) // Prints 4 4
由于没有更多元素的空间,因此将分配一个新数组。但是,您将指向此新数组的切片存储在d
中,而不是存储在a
中。 a
仍指向旧数组。
然后您执行第3次追加,但要进行a
(指向旧数组):
fmt.Println(len(a), cap(a)) // Prints 2 2
e := append(a, 3, 4)
fmt.Println(len(e), cap(e)) // Prints 4 4
同样,a
数组不能容纳更多元素,因此分配了一个新数组,并将其存储在e
中。
因此d
和e
具有不同的后备数组,并且追加到与“另一个”切片共享后备数组的任何切片都不会(不能)更改此“另一个”切片。因此,结果是您两次看到d
的相同地址,而发现e
的另一个地址。
答案 1 :(得分:1)
我在您的示例中又添加了几行,看看here。
查看第一个printSlice("a", a)
。长度为1,容量为2。添加项目时,无需分配更大的基础数组,因此b
和c
使用相同的数组。
一旦长度超过2(d := append(c, 3)
),就会为d
分配一个新的后备阵列。 c
保持不变。因此,在创建e
时,将创建另一个新的后备数组,执行相同的过程。