「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。
前言
之前讲到的数组,由于并不能拓展容量,因此其实并不是很灵活,切片在和数组一样是一块连续的内存空间,支持随机访问的同时,也可以灵活的改变容量。
数据结构
type SliceHeader struct {
Data unitptr
Len int
Cap int
}
分别代表了数据,长度和容量,其中长度代表切片中含有多少元素,容量代码切片中可以含有多少元素。 当我们不断拓展切片,使切片的长度大于其容量时,切片会遵循一些规则被扩容。
在初始化时,切片的长度和容量默认相等,当然也可以显式的指定切片的长度和容量。
切片的底层存储
切片的截取是一个常见的操作。举个例子:
foo := make([]int, 5);
foo[3] = 42
arr = foo[1:4]
arr[1] = 19
运行后发现如果修改了arr的值,foo的值也要被修改。因此我们推断,切片即使截取,依旧共享一份底层数据。
这个截取有点“引用传递”的意思了,上篇文章讲过数组无论是函数传参还是a=b这种变量的复制,都是值复制,那么切片也是么?
func TestArray(t *testing.T) {
a := []int{1, 2, 3}
b := a
c := b
c[1] = 10
t.Log(a)
}
输出为: [1 10 3]
难道切片是“引用”复制了?其实也不是,只不过切片复制的时候,复制的是上面提到的SliceHeader,因此三个SliceHeader其实里面指针指向的是其实是同一个地址。而如果复制的成本只是两个int一个指针的话,切片复制的成本就变得极低,相对于数组来说节省了大量的成本。
用切片的截取+拼接实现删除中间一个元素
arr = append(arr[:x], arr[x+1:]...)
用这种方式不需要额外的内存空间。