切片(slice)代表变长的序列,序列中每个元素都有相同的类型。一个 slice 类型一般写作 []T ,其中 T 代表 slice 中元素的类型。
切片(slice)是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以切片是一个引用类型(因此更类似于 Python 中的 list 类型)。一个 slice 由三个部分构成:指针、长度和容量。指针指向第一个 slice 元素对应的底层数组元素的地址,要注意的是 slice 的第一个元素并不一定就是数组的第一个元素。长度对应 slice 中元素的数目;长度不能超过容量,容量一般是从 slice 的开始位置到底层数据的结尾位置。内置的 lencap 函数分别返回 slice 的长度和容量。

创建和初始化

Go 语言中有几种方法可以创建和初始化切片。是否能提前知道切片需要的容量通常会决定要如何创建切片。

make

一种创建切片的方法是使用内置的 make 函数。当使用 make 时,需要传入一个参数,指定切片的长度及容量。

  1. // 创建一个字符串切片
  2. // 其长度和容量都是 5 个元素
  3. slice := make([]string, 5)
  4. // 创建一个整型切片
  5. // 其长度为 3 个元素,容量为 5 个元素
  6. slice := make([]int, 3, 5)

字面量

另一种常用的创建切片的方法是使用切片字面量。这种方法和创建数组类似,只是不需要指定 [] 运算符里的值。初始的长度和容量会基于初始化时提供的元素的个数确定。

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

需要注意,如果在 [] 运算符里指定了一个值,那么创建的就是数组而不是切片。只有不指定值的时候,才会创建切片。

  1. // 创建有 3 个元素的整型数组
  2. array := [3]int{10, 20, 30}
  3. // 创建长度和容量都是 3 的整型切片
  4. slice := []int{10, 20, 30}

有时,程序可能需要声明一个值为 nil 的切片。只要在声明时不做任何初始化,就会创建一个 nil 切片。 nil 切片可以用于很多标准库和内置函数。在需要描述一个不存在的切片时, nil 切片会很好用。

  1. // 创建 nil 整型切片
  2. var slice []int

利用初始化,通过声明一个切片可以创建一个空切片。

  1. // 使用 make 创建空的整型切片
  2. slice := make([]int, 0)
  3. // 使用切片字面量创建空的整型切片
  4. slice := []int{}

空切片在底层数组包含 0 个元素,也没有分配任何存储空间。想表示空集合时空切片很有用。需要注意 nil 切片不同于空切片,只是不管是使用 nil 切片还是空切片,对其调用内置函数 appendlencap 的效果都是一样的。

截取

通过 [:] 截取数组,也可以创建切片。

  1. array := [10]int{}
  2. // 长度和容量为 10 的切片
  3. slice1 := array[0:10]
  4. // 长度和容量为 10 的切片
  5. slice2 := array[:]
  6. // 长度为 4, 容量为 10 的切片
  7. slice3 := array[:4]
  8. // 长度为 6, 容量为 6 的切片
  9. slice4 := array[4:]
  10. // 长度为 3, 容量为 8 的切片
  11. slice5 := array[2:5]

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

  • 长度:len(slice) == j - i
  • 容量:cap(slice) == k - i

在创建切片时,还可以使用之前我们没有提及的第三个索引选项。第三个索引可以用来控制新切片的容量。其目的并不是要增加容量,而是要限制容量。可以看到,允许限制新切片的容量为底层数组提供了一定的保护,可以更好地控制追加操作。

  1. // 创建字符串切片
  2. // 其长度和容量都是 5 个元素
  3. source := []string{"Apple", "Orange", "Plum", "Banana", "Grape"}
  4. // 将第 3 个元素切片,并限制容量
  5. // 其长度为 1 个元素,容量为 2 个元素
  6. slice := source[2:3:4]
  7. // 对于 slice[i:j:k] 或 [2:3:4]
  8. // 长度: j – i 或 3 - 2 = 1
  9. // 容量: k – i 或 4 - 2 = 2

和数组不同的是, slice 之间不能比较,因此我们不能使用 == 操作符来判断两个 slice 是否含有全部相等元素。不过标准库提供了高度优化的 bytes.Equal 函数来判断两个字节型 slice 是否相等( []byte ),但是对于其他类型的 slice ,我们必须自己展开每个元素进行比较:

  1. func equal(x, y []string) bool {
  2. if len(x) != len(y) {
  3. return false
  4. }
  5. for i := range x {
  6. if x[i] != y[i] {
  7. return false
  8. }
  9. }
  10. return true
  11. }

slice 唯一合法的比较操作是和 nil 比较。

切片增长

内置的 append 函数用于向 slice 追加元素:

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

如果切片的底层数组没有足够的可用容量, append 函数会创建一个新的底层数组,将被引用的现有的值复制到新数组里,再追加新的值。

  1. // 创建一个整型切片
  2. // 其长度和容量都是 4 个元素
  3. slice := []int{10, 20, 30, 40}
  4. // 向切片追加一个新元素
  5. // 将新元素赋值为 50
  6. newSlice := append(slice, 50)

迭代切片

既然切片是一个集合,可以迭代其中的元素。Go 语言有个特殊的关键字 range ,它可以配合关键字 for 来迭代切片里的元素(for-range 结构)。

  1. // 创建一个整型切片
  2. // 其长度和容量都是 4 个元素
  3. slice := []int{10, 20, 30, 40}
  4. // 迭代每一个元素,并显示其值
  5. for index, value := range slice {
  6. fmt.Printf("Index: %d Value: %d\n", index, value)
  7. }

输出:

  1. Index: 0 Value: 10
  2. Index: 1 Value: 20
  3. Index: 2 Value: 30
  4. Index: 3 Value: 40

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