06 | 切片

102 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

引言

切片是基于数组的一种数据结构,其展示形式与数组基本一致,区别在于:切片是可动态增长的,按需使用。

内部实现

切片是一个很小的对象,对底层数组进行了抽象,底层内存也是在连续块中分配的,所以也具有获得索引、迭代以及垃圾回收优化的好处。

其数据结构包含3个字段:指向底层数组的指针、切片的长度、切片的容量。

基础功能

创建和初始化

go语言拥有好几种方式进行创建和初始化切片,容量是创建切片的方式。

slice := make([]string, 5)  // 使用make函数创建一个长度和容量均为5的字符串切片
slice := make([]string, 3, 5)   // 使用make函数创建一个长度为3且容量为5的字符串切片
// 切片始终不允许容量小于长度
slice := []string{"a", "b", "c", "d"}   // 使用字面量创建一个长度和容量均为4的字符串切片
// 字面量创建的方式和数组的区别在于[]的内容
slice := []string{10:"a"}   // 创建一个长度和容量都为11的字符串切片,且第11个元素为“a”,其余为“”
var slice []string  // 创建一个空切片,长度和容量均为0,等价于 slice := make([]string, 0)

使用切片

对切片里某个索引指向的元素赋值和对数组里某个索引指向的元素赋值的方法完全一样,使用[]操作符就可以改变某个元素的值。

slice := []int{10, 20, 30, 40, 50}  // 创建一个长度和容量都为5的整型切片
slice[1] = 200  // slice = {10, 200, 30, 40, 50}
​
newSlice := slice[1:3]  // 创建一个其长度为2且容量为4的新切片  newSlice=[200, 30]
​
//如何计算长度和容量
// 对底层数组容量是 k 的切片 slice[i:j]来说: len=j-i,cap=k-i 
​
newSlice := slice[1:3:4]    // 限制新切片的容量为 4 - 1

如何计算长度和容量?

对底层数组容量是k的切片slice[i: j]来说: len= j - i,cap=k-i

对切片slice[i: j: k]来说: len= j - i,cap=k - i

修改切片内容可能导致的结果

slice := []int{10, 20, 30, 40, 50}
newSlice := slice[1:3]
newSlice[1] = 35

创建一个长度和容量均为5的整型切片slice,创建引用slice的一个长度为2,容量为4的新切片newSlice;修改 newSlice索引为 1 的元素,同时也修改了原来的slice的索引为 2 的元素。Why?这是由于切片的数据结构产生的,因为底层数组指针指向的是同一地址。

切片增长

切片可以按需增加容量,使用内置函数append进行操作,增加新切片的长度,而容量有可能会改变,也可能不会改变,这取决于被操作的切片的可用容量,返回包含修改结果的新切片。

slice := []int{10, 20, 30, 40}  // 创建一个长度和容量均为4的整型切片
newSlice := append(slice, 50)   // 向切片追加一个新元素,将新元素赋值为 50

这个append操作完成后,newSlice拥有一个全新的底层数组,这个数组的容量是原来的两倍。函数 append 会智能地处理底层数组的容量增长。在切片的容量小于 1000 个元素时,总是会成倍地增加容量。一旦元素个数超过 1000,容量的增长因子会设为 1.25,也就是会每次增加 25% 的容量。随着语言的演化,这种增长算法可能会有所改变

迭代

切片作为一个集合,可以迭代其中的元素。Go 语言有个特殊的关键字 range,它可以配合关键字 for 来迭代切片里的元素

slice := []int{1, 2, 3, 4, 5}
for index, value := range slice{
    fmt.Printf("index:%d value:%d\n", index, value)
}

在迭代时,关键字range会返回两个值,第一个为当前迭代到的索引位置,第二个时该位置上对应元素值的副本

多维切片

和数组一样,切片是一维的。不过也可以组合多个切片形成多维切片。

函数传递切片

在函数间传递切片就是要在函数间以值的方式传递切片。由于切片的尺寸很小(数据仅占24字节),在函数间复制和传递切片成本也很低。复制时只会复制切片本身,不会涉及底层数组。