Skip to main content
  1. internet/

go中append的坑

·174 words·1 min·
Table of Contents

问题复现 #

func main() {
	a := []int{3, 1, 5, 8}
	b := append(a[:1], a[2:]...)
	fmt.Println(a)
	fmt.Println(b)
}

append(slice[:i], slice[i+1:]...)是很常见的用于去除切片slice中第i个元素的操作。打印b会得到[3,5,8].

但是打印a会显示什么呢?

理想情况是a没有任何变化,但实际情况是:

[3 5 8 8]

a被修改了!

猜想 #

依稀记得append操作会判断当前切片的容量,如何切片的容量足够容纳添加进来的值,就会复用这个切片。

因此,在操作append(a[:1], a[2:]...)时,程序发现a的容量足够,不需要扩容,因此会复用a,因此将5, 8 (即a[2]...)添加到了3 (即a[:1])后边,于是就有了3,5,8, 同时,a[3]没有被修改,因此仍是8,所以b的结果就是3, 5, 8, 8

证实 #

可以通过阅读append函数的实现代码来证实,但是append是一个内置函数,看不到底层实现。但是我们可以在官方的博客中看到其实现逻辑:

func Append(slice []int, elements ...int) []int {
    n := len(slice)
    total := len(slice) + len(elements)
    if total > cap(slice) {
        // Reallocate. Grow to 1.5 times the new size, so we can still grow.
        newSize := total*3/2 + 1
        newSlice := make([]int, total, newSize)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[:total]
    copy(slice[n:], elements)
    return slice
}

可以看到,如果旧切片的容量已经足够(len(slice) + len(elements) < cap(slice)),则不会新建切片!

也可以看到,新的元素会覆盖掉a[:1]之后的数据(copy(slice[n:], elements).

验证 #

通过打印一些关键数据,就可以验证上边的想法:

func main() {
	a := []int{3, 1, 5, 8}
	// a[:1]数据
	fmt.Printf("len:%d, cap:%d, array:%p\n", len(a[:1]), cap(a[:1]), a[:1])
	b := append(a[:1], a[2:]...)
	// 底层数组地址
	fmt.Printf("array of a: %p\n", a)
	fmt.Printf("array of b: %p\n", b)
	// 切片地址
	fmt.Printf("slice of a: %p\n", &a)
	fmt.Printf("slice of b: %p\n", &b)
}

// 终端输出
len:1, cap:4, array:0x14000130000
array of a: 0x14000130000
array of b: 0x14000130000
slice of a: 0x1400010e030
slice of b: 0x1400010e060

可以看到a和b使用的同一个底层数组,说明append之后没有产生新的底层数组。