切片(slice)代表变长的序列,序列中每个元素都有相同的类型。一个 slice 类型一般写作 []T ,其中 T 代表 slice 中元素的类型。
切片(slice)是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以切片是一个引用类型(因此更类似于 Python 中的 list 类型)。一个 slice 由三个部分构成:指针、长度和容量。指针指向第一个 slice 元素对应的底层数组元素的地址,要注意的是 slice 的第一个元素并不一定就是数组的第一个元素。长度对应 slice 中元素的数目;长度不能超过容量,容量一般是从 slice 的开始位置到底层数据的结尾位置。内置的 len 和 cap 函数分别返回 slice 的长度和容量。
创建和初始化
Go 语言中有几种方法可以创建和初始化切片。是否能提前知道切片需要的容量通常会决定要如何创建切片。
make
一种创建切片的方法是使用内置的 make 函数。当使用 make 时,需要传入一个参数,指定切片的长度及容量。
// 创建一个字符串切片// 其长度和容量都是 5 个元素slice := make([]string, 5)// 创建一个整型切片// 其长度为 3 个元素,容量为 5 个元素slice := make([]int, 3, 5)
字面量
另一种常用的创建切片的方法是使用切片字面量。这种方法和创建数组类似,只是不需要指定 [] 运算符里的值。初始的长度和容量会基于初始化时提供的元素的个数确定。
// 创建字符串切片// 其长度和容量都是 5 个元素slice := []string{"Red", "Blue", "Green", "Yellow", "Pink"}// 创建一个整型切片// 其长度和容量都是 3 个元素slice := []int{10, 20, 30}// 创建字符串切片// 使用空字符串初始化第 100 个元素slice := []string{99: ""}
需要注意,如果在 [] 运算符里指定了一个值,那么创建的就是数组而不是切片。只有不指定值的时候,才会创建切片。
// 创建有 3 个元素的整型数组array := [3]int{10, 20, 30}// 创建长度和容量都是 3 的整型切片slice := []int{10, 20, 30}
有时,程序可能需要声明一个值为 nil 的切片。只要在声明时不做任何初始化,就会创建一个 nil 切片。 nil 切片可以用于很多标准库和内置函数。在需要描述一个不存在的切片时, nil 切片会很好用。
// 创建 nil 整型切片var slice []int
利用初始化,通过声明一个切片可以创建一个空切片。
// 使用 make 创建空的整型切片slice := make([]int, 0)// 使用切片字面量创建空的整型切片slice := []int{}
空切片在底层数组包含 0 个元素,也没有分配任何存储空间。想表示空集合时空切片很有用。需要注意 nil 切片不同于空切片,只是不管是使用 nil 切片还是空切片,对其调用内置函数 append 、 len 和 cap 的效果都是一样的。
截取
通过 [:] 截取数组,也可以创建切片。
array := [10]int{}// 长度和容量为 10 的切片slice1 := array[0:10]// 长度和容量为 10 的切片slice2 := array[:]// 长度为 4, 容量为 10 的切片slice3 := array[:4]// 长度为 6, 容量为 6 的切片slice4 := array[4:]// 长度为 3, 容量为 8 的切片slice5 := array[2:5]
对底层数组容量是 k 的切片 slice = array[i:j] 来说:
- 长度:
len(slice) == j - i; - 容量:
cap(slice) == k - i。
在创建切片时,还可以使用之前我们没有提及的第三个索引选项。第三个索引可以用来控制新切片的容量。其目的并不是要增加容量,而是要限制容量。可以看到,允许限制新切片的容量为底层数组提供了一定的保护,可以更好地控制追加操作。
// 创建字符串切片// 其长度和容量都是 5 个元素source := []string{"Apple", "Orange", "Plum", "Banana", "Grape"}// 将第 3 个元素切片,并限制容量// 其长度为 1 个元素,容量为 2 个元素slice := source[2:3:4]// 对于 slice[i:j:k] 或 [2:3:4]// 长度: j – i 或 3 - 2 = 1// 容量: k – i 或 4 - 2 = 2
和数组不同的是, slice 之间不能比较,因此我们不能使用 == 操作符来判断两个 slice 是否含有全部相等元素。不过标准库提供了高度优化的 bytes.Equal 函数来判断两个字节型 slice 是否相等( []byte ),但是对于其他类型的 slice ,我们必须自己展开每个元素进行比较:
func equal(x, y []string) bool {if len(x) != len(y) {return false}for i := range x {if x[i] != y[i] {return false}}return true}
切片增长
内置的 append 函数用于向 slice 追加元素:
// 创建一个整型切片// 其长度和容量都是 5 个元素slice := []int{10, 20, 30, 40, 50}// 创建一个新切片// 其长度为 2 个元素,容量为 4 个元素newSlice := slice[1:3]// 使用原有的容量来分配一个新元素// 将新元素赋值为 60newSlice = append(newSlice, 60)
如果切片的底层数组没有足够的可用容量, append 函数会创建一个新的底层数组,将被引用的现有的值复制到新数组里,再追加新的值。
// 创建一个整型切片// 其长度和容量都是 4 个元素slice := []int{10, 20, 30, 40}// 向切片追加一个新元素// 将新元素赋值为 50newSlice := append(slice, 50)
迭代切片
既然切片是一个集合,可以迭代其中的元素。Go 语言有个特殊的关键字 range ,它可以配合关键字 for 来迭代切片里的元素(for-range 结构)。
// 创建一个整型切片// 其长度和容量都是 4 个元素slice := []int{10, 20, 30, 40}// 迭代每一个元素,并显示其值for index, value := range slice {fmt.Printf("Index: %d Value: %d\n", index, value)}
输出:
Index: 0 Value: 10Index: 1 Value: 20Index: 2 Value: 30Index: 3 Value: 40
当迭代切片时,关键字 range 会返回两个值。第一个值是当前迭代到的索引位置,第二个值是该位置对应元素值的一份副本。
