go语言切片系列(一)

1,554 阅读2分钟

「这是我参与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:]...)

用这种方式不需要额外的内存空间。