Golang 踩坑之切片的陷阱

Jackey Golang 819 次浏览 , 没有评论

以下两个函数执行结果一样吗?为什么?

func f2() {
    ia := [...]int{1, 2, 3, 4, 5}
    ia2 := ia[1:3]
    for i := 6; i < 10; i++ {
        ia2 = append(ia2, i)
    }
    fmt.Println(ia, ia2)
}

func f1() {
    ia := [...]int{1, 2, 3, 4, 5}
    ia2 := ia[1:3]
    ia2 = append(ia2, 6,7,8,9)
    fmt.Println(ia, ia2)
}

踩坑分析:

切片(slice)陷阱
考虑到性能原因,再次切片一个切片不会复制底层的数组。这是一个值得赞赏的目标,但也意味着切片的子切片只是遵循原始切片变化的视图。因此,如果您想要将它与初始的切片分开请不要忘记 copy()。

对于 append 函数,忘记 copy() 会变得更加危险:如果它没有足够的容量来保存新值,底层数组将会重新分配内存和大小。这意味着 append 的结果能不能指向原始数组取决于它的初始容量。这会导致难以发现的不确定 bugs。

分别观察一下f1和f2函数的输出,f1函数里的是直接开辟了一块新的内存去存储,f2函数里的是先在原基础上上写的。

f1中ia2 := ia[1:3]后cap(ia2)的长度是4,因为ia2 = append(ia2, 6,7,8,9),会超出4,所以直接分配一块内存,在新的内存上写。

f2中ia2 := ia[1:3]后cap(ia2)的长度是4,因为逐个写的,第一次append(ia2, i)之后,没有超出4,第二次操作也没有,所以改动了原来的ia,第三次的时候,超出了4,就重新分配一个块cap(ia2)为8的内存上,并把值拷贝过去。

func f2() {
    ia := [...]int{1, 2, 3, 4, 5}
    ia2 := ia[1:3]
    fmt.Printf("len: %d, cap: %d\n",len(ia2), cap(ia2))
    fmt.Printf("%v %v\n", unsafe.Pointer(&ia[0]), unsafe.Pointer(&ia2[0]))
    fmt.Printf("%v %v\n", unsafe.Pointer(&ia[1]), unsafe.Pointer(&ia2[1]))
    fmt.Printf("%v\n", unsafe.Pointer(&ia[2]))
    fmt.Printf("%v\n", unsafe.Pointer(&ia[3]))
    fmt.Printf("%v\n", unsafe.Pointer(&ia[4]))
    for i := 6; i < 10; i++ {
        fmt.Println("------------")
        ia2 = append(ia2, i)
        fmt.Printf("len: %d, cap: %d\n",len(ia2), cap(ia2))
        fmt.Printf("%v %v\n", unsafe.Pointer(&ia), unsafe.Pointer(&ia2))
        fmt.Printf("%v %v\n", unsafe.Pointer(&ia[1]), unsafe.Pointer(&ia2[1]))
        if len(ia2) < 3 {
            fmt.Printf("%v\n", unsafe.Pointer(&ia[2]))
        } else {
            fmt.Printf("%v %v\n", unsafe.Pointer(&ia[2]), unsafe.Pointer(&ia2[2]))
        }
        if len(ia2) < 4 {
            fmt.Printf("%v\n", unsafe.Pointer(&ia[3]))
        } else {
            fmt.Printf("%v %v\n", unsafe.Pointer(&ia[3]), unsafe.Pointer(&ia2[3]))
        }
        if len(ia2) < 5 {
            fmt.Printf("%v\n", unsafe.Pointer(&ia[4]))
        } else {
            fmt.Printf("%v %v\n", unsafe.Pointer(&ia[4]), unsafe.Pointer(&ia2[4]))
        }
        if len(ia2) == 6 {
            fmt.Printf("%v\n", unsafe.Pointer(&ia2[5]))
        }
    }
    fmt.Println(ia, ia2)
}

func f1() {
    ia := [...]int{1, 2, 3, 4, 5}
    ia2 := ia[1:3]
    fmt.Printf("%v %v\n", unsafe.Pointer(&ia), unsafe.Pointer(&ia2))
    fmt.Printf("%v %v\n", unsafe.Pointer(&ia[0]), unsafe.Pointer(&ia2[0]))
    fmt.Printf("%v %v\n", unsafe.Pointer(&ia[1]), unsafe.Pointer(&ia2[1]))
    fmt.Printf("%v\n", unsafe.Pointer(&ia[2]))
    fmt.Printf("%v\n", unsafe.Pointer(&ia[3]))
    fmt.Printf("%v\n", unsafe.Pointer(&ia[4]))
    fmt.Println("------------")
    ia2 = append(ia2, 6,7,8,9)
    fmt.Printf("%v %v\n", unsafe.Pointer(&ia), unsafe.Pointer(&ia2))
    fmt.Printf("%v %v\n", unsafe.Pointer(&ia[0]), unsafe.Pointer(&ia2[0]))
    fmt.Printf("%v %v\n", unsafe.Pointer(&ia[1]), unsafe.Pointer(&ia2[1]))
    fmt.Printf("%v %v\n", unsafe.Pointer(&ia[2]), unsafe.Pointer(&ia2[2]))
    fmt.Printf("%v %v\n", unsafe.Pointer(&ia[3]), unsafe.Pointer(&ia2[3]))
    fmt.Printf("%v %v\n", unsafe.Pointer(&ia[4]), unsafe.Pointer(&ia2[4]))
    fmt.Printf("%v\n", unsafe.Pointer(&ia2[5]))
    fmt.Println(ia, ia2)
}

文章参考来源:https://studygolang.com/articles/35050

发表评论

您的电子邮箱地址不会被公开。

Go