Go中的切片-1

721 阅读3分钟

世界上没有比快乐更能使人美丽的化妆品。——布雷顿

切片的内部实现

切片是一个很小的对象,对底层数组进行了抽象,并提供相关的操作方法。

可以看到切片有三个元数据,一个是指向底层数组头部的指针、一个代表切片的长度、一个代表切片的容量。

创建切片

使用make创建。

slice1 := make([]int) // nil
slice2 := make([]int, 5) // {0, 0, 0, 0, 0} 因为int的空值为0
slice3 := make([]int, 5, 10) // {0, 0, 0, 0, 0} 可以增长到10个元素

这里看起来slice2slice3值的区别不大,但是当谁用append方式,容器这个元数据决定,是否新建一个底层数组,下面有具体说的地方。

容量小于长度的切片会在编译时报错

字面量创建

// 创建字符串切片
// 其长度和容量都是 5 个元素
slice := []string{"Red", "Blue", "Green", "Yellow", "Pink"}
// 创建一个整型切片
// 其长度和容量都是 3 个元素
slice := []int{10, 20, 30}
// 创建字符串切片
// 使用空字符串初始化第 100 个元素
slice := []string{99: ""}

如果在创建的时候[]写了值,例如[6],这样创建的是数组,而不是切片

nil 和空切片

因为切片也是属于指针的,所以它的空值就是nil

var slice []int
// slice == nil

var slice1 []int{}
// slice1 != nil
len(slice1) == 0 // true

使用切片创建切片

// 创建一个整型切片
// 其长度和容量都是 5 个元素
slice := []int{10, 20, 30, 40, 50}
// 创建一个新切片
// 其长度为 2 个元素,容量为 4 个元素
newSlice := slice[1:3]

执行完代码清单 4-25 中的切片动作后,我们有了两个切片,它们共享同一段底层数组,但通过不同的切片会看到底层数组的不同部分(见图 4-12)。

使用 append 向切片增加元素

// 创建一个整型切片
// 其长度和容量都是 5 个元素
slice := []int{10, 20, 30, 40, 50}
// 创建一个新切片
// 其长度为 2 个元素,容量为 4 个元素
newSlice := slice[1:3]
// 使用原有的容量来分配一个新元素
// 将新元素赋值为 60
newSlice = append(newSlice, 60)

结果如下

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

看如下代码

slice := []int{1, 2, 3, 4}
newSlice := append(slice, 5, 6) // 增加两个,因为原来slice的容器只有4,但是这里增加了2个,容量不够了,所以就自动的产生了新的底层数组,新的切片的容量变成了8。
newSlice2 := append(newSlice, 6) // 这里在newSlice的基础上增加一个元素,但是没有超过newSlice的容量8,所以不会产生新的底层数组,newSlice2和newSlice使用同一个底层数组。

newSlice2[0] = 10 // 观察前两个切片的值
fmt.Println(newSlice2[0]) // 10
fmt.Println(slice[0]) // 1
fmt.Println(newSlice[0]) // 10

函数传递

在函数间传递数组的时候,是复制的数组的值,如果数组过多,就会消耗过多内存,所以数组一般会使用func(arr *[]int)指针的形式传递。但是切片区别于数组,它只是一个指向数组的指针,所以就算底层数组很大,在函数传递的时候,只会复制切片(指针),不会复制底层数组,所以传输效率很高。

文中部分代码和图片来着《Go语言实战》

感谢作者