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 ) 如果dstSlice 是一个len=0的slice, 是无法复制进去的。 正确copy操作: 零值是未初始化的时候,的值。 切片(结构体)的零值是nil。 make函数创建的切片,其实已经初始化了一块内存给切片。 srcSlice := []int{0, 1, 2, 3}
dstSlice := make([]int, 2)
copy(dstSlice, srcSlice)
fmt.Println(dstSlice)// 结果:[0 1]
srcSlice := []int{0, 1, 2, 3}
dstSlice := make([]int, 0)
copy(dstSlice, srcSlice)
fmt.Println(dstSlice) // 结果:[]
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
s := make([]int, 0)
fmt.Printf("%t\n", s) //[]
fmt.Println(s == nil) //false