切片(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]
// 使用原有的容量来分配一个新元素
// 将新元素赋值为 60
newSlice = append(newSlice, 60)
如果切片的底层数组没有足够的可用容量, append
函数会创建一个新的底层数组,将被引用的现有的值复制到新数组里,再追加新的值。
// 创建一个整型切片
// 其长度和容量都是 4 个元素
slice := []int{10, 20, 30, 40}
// 向切片追加一个新元素
// 将新元素赋值为 50
newSlice := 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: 10
Index: 1 Value: 20
Index: 2 Value: 30
Index: 3 Value: 40
当迭代切片时,关键字 range
会返回两个值。第一个值是当前迭代到的索引位置,第二个值是该位置对应元素值的一份副本。