5.3. 切片
切片类型的基本声明如下,T
为元素类型:
[]T
切片的第一印象与数组很相似:它们都是特定类型对象的序列,但它们的实际行为存在巨大区别,切片是数组的部分引用,它时常取自于数组,例如:
arr := [...]i32{1, 2, 3, 4}
sl: []i32 = arr[0:2]
println(len(sl)) // 2
println(sl[0], sl[1]) // 1 2
表达式 arr[m:n]
返回一个切片,切片始于数组 arr
的第 m
个元素,切片长度为 n-m
,与字符串的类似语法相似,若省略 m
,则表示始于数组首个元素;若省略 n
,则终于数组最后一个元素。m
和 n
不可超过数组实际范围,否则会触发异常。
切片中并不保存实际数据,通过 []
访问到的对象位于它所引用的数组中,这意味着更改数组中的对象可能影响到切片,反之亦然,例如:
arr := [...]i32{1, 2, 3, 4}
sl := arr[0:2]
println(sl[0]) // 1
arr[0] = 13
println(sl[0]) // 13
sl[1] = 42
println(arr[1]) // 42
内置函数 cap
可用于获取切片的可用容量——既切片所引用的数组的长度减去切片开始位置,例如:
arr := [...]i32{11, 12, 13, 14}
sl1 := arr[1:2]
println(len(sl1), cap(sl1)) // 1 3
由定义可知,切片的容量恒大等于其长度。
一个数组可以被多个切片引用,如果引用的部分之间存在重叠,那么重叠部分的更改也会互相影响,例如:
arr := [...]i32{1, 2, 3, 4}
sl1 := arr[0:2]
sl2 := arr[1:3]
println(sl2[0]) // 2
sl1[1] = 42
println(sl2[0]) // 42
实际上,对切片使用[m:n]
操作符也可以获得一个新的切片,新切片始于源切片的第 m
个元素,其余规则与从数组中获取一个切片类似。
获取切片的方法除了引用数组或已有切片外,还可以通过内置函数 make
直接创建,形式签名为:
make([]T, Len: int, Cap: int) => []T
make([]T, Len: int) => []T // 等价于 make([]T, Len, Len)
返回值是一个类型为 []T
、长度为 Len
、容量为 Cap
的切片,其中 Cap
可以省略,此时切片的容量为 Len
,例如:
sl1 := make([]i32, 3, 5)
println(sl1[0], len(sl1), cap(sl1)) // 0 3 5
使用 make
函数创建切片时,隐式的创建了一个长度为 Cap
的数组,并将其引用为切片。
另一个与切片密切相关的内建函数是 append
,它用于向切片中追加元素,形式签名为:
append(sl []T, e T) => []T
该函数将元素 e
追加至切片 s
的尾部,并返回一个新的切片。由于凹语言的函数调用使用值传递,追加行为不会影响源切片 s
,因此实际常用的写法如下例:
sl: []i32
//...
sl = append(sl, 42)
既将 append
返回的新切片赋值给源切片。append
不仅向切片中追加元素,还可以追加另一个切片,例如:
sl1 := []i32{13, 42}
sl2 := []i32{9527, 1024}
sl1 = append(sl1, sl2...)
println(sl1[0], sl1[1], sl1[2], sl1[3]) // 13 42 9527 1024
当被追加对象是切片时,应在变量名后添加 ...
。
由于切片底层引用了一个长度固定的数组,如果使用 append
追加元素后,切片的长度未超过数组可用容量,那么数组对应元素的内容将被替换为追加元素,例如:
arr := [...]i32{1, 2, 3}
sl1 := arr[0:2]
sl1 = append(sl1, 5)
println(arr[2]) // 5
倘若新切片的长度超过原始数组容量,那么 append
函数会自动重新申请一个足够大的数组,将源切片的元素拷贝至新数组(既自动执行了一次深拷贝),然后再执行追加,这种情况我们称为切片扩容,例如:
arr := [...]i32{1, 2, 3}
sl1 := arr[:]
sl2 := append(sl1, 4)
sl2[0] = 42
println(sl1[0]) // 1
显然,如果发生了切片扩容,新切片与源切片的相互引用关系就切断了。
与前面章节介绍过的数据类型不同,切片类型的变量不可比较,因为切片不是纯值类型,而是与底层数组甚至其他切片存在引用关系,这种关联使得切片在运行时无法保证其中的元素值不变。
切片只能与常量 nil
进行比较,用于判断切片是否为 0 值,例如:
sl: []i32
println(sl == nil, sl != nil) // true false
实际上,如果需要判断某个切片是否为空,不应将其与 nil
比较,而应判断其长度是否为 0,因为存在长度为 0,但不为 nil
的切片,例如:
arr := [...]i32{1, 2, 3}
sl1 := arr[0:0]
println(sl1 == nil, len(sl1), cap(sl1)) // false 0 3
除特别说明外,凹语言程序应以相同的方式处理长度为 0 的切片,与 nil
值的切片。内置函数 append
既符合该要求,例如下列程序是合法的:
sl: []i32 // sl == nil
sl := append(sl, 5)
println(sl[0]) // 5
已知问题:
- 访问切片元素时,未执行边界检查。该问题不影响语法兼容性,后续对本问题的修正不影响已有源代码,凹程序开发者无须对此进行特别处理。
- 使用
[]
从数组或切片中获取新切片时,未执行边界检查。该问题不影响语法兼容性,后续对本问题的修正不影响已有源代码,凹程序开发者无须对此进行特别处理。