Go 提供了通过索引访问数组和切片中单个元素的能力。这些类型被视为有序序列。
除了访问切片的特定元素之外,还可以使用基于索引的操作对切片进行其他操作。在本节中,你将学习如何使用切片表达式来执行这些操作。
简单切片表达式
在 Go 中,我们可以使用切片表达式执行一系列操作。首先来看一下常用的简单切片表达式语法,用于构造子切片:
s[low:high]
代码解析
索引 low 和 high 用于选择切片 s 中哪些元素会出现在结果中。下面我们看一个例子来创建子切片 s1:
s := []int{2, 3, 5, 7, 11} // len: 5 cap: 5
s1 := s[1:3]
fmt.Println(s1) // [3 5]
代码解析
注意,索引 low 对应的元素 s[1] = 3 会被包含在新的子切片 s1 中,而索引 high 对应的元素 s[3] = 7 不会被包含。简单来说,high 是切片元素的范围边界。
新创建的子切片 s1 的长度 len 等于索引范围 high - low -> 3 - 1 = 2,容量 cap 等于 cap(s) - low -> 5 - 1 = 4:
fmt.Println("len:", len(s1), "cap:", cap(s1)) // len: 2 cap: 4
为了方便,可以省略任意一个索引。缺省的 low 默认为 0,缺省的 high 默认为切片的总长度:
fmt.Println(s[2:]) // [5 7 11] — 等同于 s[2:len(s)]
fmt.Println(s[:3]) // [2 3 5] — 等同于 s[0:3]
fmt.Println(s[:]) // [2 3 5 7 11] — 等同于 s[0:len(s)]
由于字符串也是字符序列,我们可以使用相同的语法从字符串构造子串:
star := "Polaris"
s2 := star[0:5] + " Bear"
fmt.Println(s2) // Polar Bear
planets := []string{"Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"}
s3 := planets[5][0:5] + "day"
fmt.Println(s3) // Saturday
简单切片表达式的细节
在使用简单切片表达式获取数组或字符串的子切片时,索引 low 和 high 必须满足范围:
0 <= low <= high <= len(s)
否则会出现运行时错误。例如:
star := "Polaris"
fmt.Println(star[-1]) // 无效参数:索引 -1
fmt.Println(star[0:8]) // 运行时错误:slice bounds out of range [:8] with length 7
当从切片获取子切片时,结果切片的容量 cap 等于基础切片的容量减去 low(即 cap(s) - low),而不是基础切片的长度:
teams := []string{"Bayern Munich", "Real Madrid", "Manchester City"}
t := teams[1:2]
fmt.Println(t, "| len:", len(t), "cap:", cap(t)) // [Real Madrid] | len: 1 cap: 2
fmt.Println(t[1:3]) // 运行时错误:slice bounds out of range [:3] with capacity 2
简单切片表达式支持数组、切片、字符串,以及指向数组/切片的指针:
consts := new([3]float64)
*consts = [3]float64{3.1416, 2.718, 1.618} // 指向数组
fmt.Println(consts[1:3]) // [2.718 1.618]
animals := new([]string)
*animals = []string{"cat", "dog", "fish"} // 指向切片
fmt.Println((*animals)[1:3]) // ["dog" "fish"]
当切片为 nil 时,获取子切片的结果仍然是 nil:
var n []int // nil 切片
fmt.Println(n[:]) // []
获取子切片的子切片
一个重要细节是,子切片会共享其“父切片”的底层数组。例子如下:
var nums [5]int
nums = [5]int{1, 2, 3, 4, 5}
n1 := nums[0:4] // n1 的底层数组是 nums,n1[2] == nums[2]
n2 := n1[0:3] // n2 的底层数组是 n1,n2[2] == n1[2]
n2[2] = 343 // n2[2] == n1[2] == nums[2]
因此,n2[2] = 343 会更新三个切片的相应元素:
fmt.Println(nums) // [1 2 343 4 5]
fmt.Println(n1) // [1 2 343 4]
fmt.Println(n2) // [1 2 343]
完整切片表达式
完整切片表达式相比简单切片表达式,多了一个额外索引 max:
s[low:high:max]
它的作用是构造一个长度与简单切片表达式相同,但容量可以通过 max 控制的子切片:
s := []int{2, 3, 5, 7, 11}
s4 := s[1:3:4]
fmt.Println(s4, "| len:", len(s4), "cap:", cap(s4)) // [3 5] | len: 2 cap: 3
与简单切片表达式类似,len = high - low,但容量 cap = max - low -> 4 - 1 = 3。
完整切片表达式仅支持数组、切片或指向数组的指针,不支持字符串,否则会报错:
star := "Polaris"
fmt.Println(star[1:3:2]) // 错误:3-index slice of string
完整切片表达式的索引规则
完整切片表达式索引必须满足:
0 <= low <= high <= max <= cap(s)
否则会出错:
s := []int{2, 3, 5, 7, 11}
fmt.Println(s[1:3:2]) // 错误:2 < 3
fmt.Println(s[1:3:6]) // 运行时错误:slice bounds out of range [::6] with capacity 5
在完整切片表达式中,只有 low 可以省略,缺省为 0;但 high 和 max 不允许省略:
fmt.Println(s[:3:5]) // [2 3 5]
fmt.Println(s[1::5]) // 错误:必须提供中间索引
fmt.Println(s[1:3:]) // 错误:必须提供最后索引
总结
本节你学习了 Go 中的切片表达式,主要内容包括:
-
简单切片表达式及其索引
low和high; -
如何通过简单切片表达式获取子切片和子串;
-
子切片共享父切片的底层数组;
-
完整切片表达式及其索引
low、high、max; -
完整切片表达式不支持字符串,只能用于切片、数组或指向数组的指针。
1053

被折叠的 条评论
为什么被折叠?



