在阅读Go slice时,我在append方法的上下文中遇到了此行为
append
如果s的后备数组太小而无法容纳所有给定值,则会分配更大的数组。返回的切片将指向新分配的数组。 来源-高朗之旅
为了理解这一点,我编写了以下代码:
尝试去游乐场
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指向相同的基础数组,因为它们都是相同长度的切片
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他们再次同长度的片,但他们不应该指向了同一个后备数组。
d
e
为什么这种行为异常?Go何时确切决定将新的支持数组分配给切片?
答案很简单:append()如果要添加的元素不适合当前容量,则分配一个新的支持数组(并复制当前内容)。正式地:
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。因此,如果对进行第3次追加a,则仍然具有length = 1和cap = 2,因此将另一个元素追加到a不需要分配:
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 仍然指向旧数组。
然后,执行第三个附加操作,但是要附加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。