切片slice的几个要注意的坑

1.切片的底层实现:

在runtime包下的slice.go中我们可以看到切片的底层实现。
切片其实是一个结构体,对应有三个字段。我要注意的是第一个字段,它是一个数组指针,指向对应的数组存储空间。

type slice struct {
    array unsafe.Pointer //数组指针
    len   int //切片长度
    cap   int //切片容量
}

直接声明的切片 不能使用下标赋值。因为此时并未分配给切片存储空间。但可以使用append函数赋值,append函数会使切片自动扩容,分配存储空间。

    var slice []int
    //slice[0] = 1 //panic: runtime error: index out of range
    slice = append(slice, 1)
    fmt.Println(slice)// 输出:[1]

2.切片的扩容:

只有执行append函数时,切片才会扩容。
当切片的长度len等于容量cap时,再添加数据就会将当前的数据复制到一个更大的数组中,自动扩容。容量就会成倍增加: 如果数据长度在1024以下的每次扩容到原数组长度的2倍。超出1024时扩容为上一次的1.25倍-1.33倍(1/3-1/4之间)(官网给出的是1/4左右)。增加的少了是怕占用的内存过大。
一道面试题eg:

    s := []int{7} //[7]
    s = append(s, 1) //[7 1]
    s = append(s, 2)   //此时切片s的len=3;cap=3 [7 1 2]
    x := append(s, 10) //此时是对 切片[7 1 2]的操作
    y := append(s, 20) //此时也是对 切片[7 1 2]的操作
    //x,y两个切片底层的数组指针 指向的是同一块数组存储空间 所以上述语句执行完后 x,y的输出是一样的。
    //但是注意当cap容量发生变化时,数组指针(地址)改变,例如 y := append(s, 20,30)时x,y的输出就会不一样 [7 1 2 10] [7 1 2 20 30]
    fmt.Println(s, x, y)
    //输出结果:[7 1 2] [7 1 2 20] [7 1 2 20]

3.切片作为函数参数传参:

func main() {
    slice := []int{0, 1, 2, 3}
    modifyA(slice)
    modifyB(slice)
    fmt.Println(slice)
    fmt.Println("-----------------------------------")
    modifyB(slice[0:2])
    fmt.Println(slice)

    modifyC(slice[0:2:2])
    fmt.Println(slice)
    modifyD(slice)
    w := slice[2:]
    w[0] = 9
    fmt.Println("w: ", w)
    fmt.Println("slice: ", slice)
}
func modifyA(s []int) {
    s[1] = 99
    fmt.Println("modifyA slice :", s)
}
func modifyB(s []int) {
    fmt.Println("==modifyB slice :", s, "cap:", cap(s))
    s = append(s, 8)
    fmt.Println("==modifyB slice :", s, "cap:", cap(s))
}
func modifyC(s []int) {
    s = append(s, 4)
    fmt.Println("modifyC slice :", s)
}
func modifyD(s []int) {
    s[0] = 3
    fmt.Println("modifyD slice :", s)
}

输出结果:

modifyA slice : [0 99 2 3]
==modifyB slice : [0 99 2 3] cap: 4
==modifyB slice : [0 99 2 3 8] cap: 8
[0 99 2 3]
-----------------------------------
==modifyB slice : [0 99] cap: 4
==modifyB slice : [0 99 8] cap: 4
[0 99 8 3]
modifyC slice : [0 99 4]
[0 99 8 3]
modifyD slice : [3 99 8 3]
w:  [9 3]
slice:  [3 99 9 3]

谨记:
1. 切片作为函数参数是地址传递(引用传递), 形参可以修改实参的值。
2. 切片的截取操作还是在源切片上操作,所以修改截取后的切片中的值 会影响源切片中对应下标的值。
3.切片作为函数参数传参时,如果在传入的函数中使用append函数使切片扩容量(切片容量大小发生改变),此时系统底层会把原数组的值复制给一个新的len更大的数组。所以对应切片指向数组的指针(地址)就会改变。这会导致形参改变,实参并未改变。

4.切片的拷贝:

len(dstSlice )>len(srcSlice )时:

    srcSlice := []int{0, 1, 2, 3}
    dstSlice := make([]int, 6)
    copy(dstSlice, srcSlice)
    fmt.Println(dstSlice) // 结果:[0 1 2 3 0 0]

len(dstSlice )

    srcSlice := []int{0, 1, 2, 3}
    dstSlice := make([]int, 2)
    copy(dstSlice, srcSlice)
    fmt.Println(dstSlice)// 结果:[0 1]

如果dstSlice 是一个len=0的slice, 是无法复制进去的。

    srcSlice := []int{0, 1, 2, 3}
    dstSlice := make([]int, 0)
    copy(dstSlice, srcSlice)
    fmt.Println(dstSlice) // 结果:[]

正确copy操作:

    srcSlice := []int{0, 1, 2, 3}
    dstSlice := make([]int, len(srcSlice))
    copy(dstSlice, srcSlice)
    fmt.Println(dstSlice)

5.切片的零值:

零值是未初始化的时候,的值。

    var s []int
    fmt.Printf("%t\n", s) //[]
    fmt.Println(s == nil) //true

切片(结构体)的零值是nil。

    s := make([]int, 0)
    fmt.Printf("%t\n", s) //[]
    fmt.Println(s == nil) //false

make函数创建的切片,其实已经初始化了一块内存给切片。

你可能感兴趣的:(切片slice的几个要注意的坑)