12.1 切片

切片(slice) 是对底层数组一个连续片段的引用,所以切片是一个引用类型。切片提供对该数组中编号的元素序列的访问。未初始化切片的值为nil。

与数组一样,切片是可索引的并且具有长度。切片s的长度可以通过内置函数len() 获取;与数组不同,切片的长度可能在执行期间发生变化。元素可以通过整数索引0到len(s)-1来寻址。我们可以把切片看成是一个长度可变的数组。

切片提供了计算容量的函数 cap() ,可以测量切片最大长度。切片的长度永远不会超过它的容量,所以对于切片 s 来说,这个不等式永远成立:0 <= len(s) <= cap(s)

一旦初始化,切片始终与保存其元素的基础数组相关联。因此,切片会和与其拥有同一基础数组的其他切片共享存储;相比之下,不同的数组总是代表不同的存储。

切片下面的数组可以延伸超过切片的末端。容量是切片长度与切片之外的数组长度的总和。

使用内置函数 make() 可以给切片初始化,该函数指定切片类型和指定长度和可选容量的参数。

因为切片是引用,所以它们不需要使用额外的内存并且比使用数组更有效率,所以在 Go 代码中切片比数组更常用。

声明切片的格式是: var identifier []type(不需要说明长度)。一个切片在未初始化之前默认为 nil,长度为 0。

切片的初始化格式是:

var slice1 []type = arr1[start:end]

这表示 slice1 是由数组 arr1 从 start 索引到 end-1 索引之间的元素构成的子集(切分数组,start:end 被称为切片表达式)。

切片也可以用类似数组的方式初始化:

var x = []int{2, 3, 5, 7, 11}

这样就创建了一个长度为 5 的数组并且创建了一个相关切片

当相关数组还没有定义时,我们可以使用 make() 函数来创建一个切片,同时创建好相关数组:

var slice1 []type = make([]type, len,cap)

也可以简写为 slice1 := make([]type, len),这里 len 是数组的长度并且也是切片的初始长度。cap是容量,其中cap是可选参数。

v := make([]int, 10, 50)

这样分配一个有 50 个int值的数组,并且创建了一个长度为10,容量为50的切片 v,该切片指向数组的前 10 个元素。

以上我们列举了三种切片初始化方式,这三种方式都比较常用。

如果从数组或者切片中生成一个新的切片,我们可以使用下面的表达式:

a[low : high : max] max-low的结果表示容量,high-low的结果表示长度。

a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5]

这里t的容量(capacity)是5-1=4 ,长度是2。

如果切片取值时索引值大于长度会导致panic错误发生,即使容量远远大于长度也没有用,如下面代码所示:

package main
import "fmt"
func main() {
	sli := make([]int, 5, 10)
	fmt.Printf("切片sli长度和容量:%d, %d\n", len(sli), cap(sli))
	fmt.Println(sli)
	newsli := sli[:cap(sli)]
	fmt.Println(newsli)
	var x = []int{2, 3, 5, 7, 11}
	fmt.Printf("切片x长度和容量:%d, %d\n", len(x), cap(x))
	a := [5]int{1, 2, 3, 4, 5}
	t := a[1:3:5] // a[low : high : max]  max-low的结果表示容量  high-low为长度
	fmt.Printf("切片t长度和容量:%d, %d\n", len(t), cap(t))
	// fmt.Println(t[2]) // panic ,索引不能超过切片的长度
}
程序输出:
切片sli长度和容量:5, 10
[0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0]
切片x长度和容量:5, 5
切片t长度和容量:2, 4
下一节:通过改变切片长度得到新切片的过程称之为切片重组 reslicing。