切片
在 Golang 当中实际上很少会用到数组,大部分情况下会直接使用切片(slices)。
Golang 当中的切片与 Python 当中的切片非常的相似(左开右闭区间),它是 array 的一个 view(视图),因此对切片进行的修改实际上会直接修改对应的 array。
一段切片的声明如下:
arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 = arr[2:6] // 2, 3, 4, 5
一段完整的用法展示如下:
package main
import "fmt"
func updateSlice(s []int) {
s[0] = 100
}
func main() {
/*
Go 当中很少使用数组
实际上更常使用的是切片(slice)
*/
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println("arr[2:6] =", arr[2:6]) // 左开右闭区间
fmt.Println("arr[:6] =", arr[:6]) // 左开右闭区间
s1 := arr[2:]
fmt.Println("s1 =", s1) // 左开右闭区间
s2 := arr[:] // 相当于直接将数组转为 slice
fmt.Println("s2", s2) // 左开右闭区间
// slice 是对 array 的 view(视图)
fmt.Println("After updateSlice(s1)")
updateSlice(s1)
fmt.Println("s1 =", s1)
fmt.Println("arr =", arr) // 由于 s1 是对 array 的 view
// 因此 array 的值也被修改了
fmt.Println("After updateSlice(s2)")
updateSlice(s2)
fmt.Println("s2 =", s2)
fmt.Println("arr =", arr)
// slice 还有一个操作叫 reslice
// 此时 s2 是一个 slice
fmt.Println("Reslice")
fmt.Println(s2)
s2 = s2[:5]
fmt.Println(s2)
s2 = s2[2:]
fmt.Println(s2)
}
切片的特性
以下例为例:
package main
import "fmt"
func main() {
fmt.Println("Extending Slice: ")
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println("arr =", arr)
s1 := arr[2:6] // [2, 3, 4, 5]
s2 := s1[3:5] // [5, 6]
fmt.Println("s1 =", s1)
fmt.Println("s2 =", s2) // 不会报错, s2 仍然取得出来
/* 为了理解上述行为为什么是合法的, 涉及到 Slice 的拓展知识
slice 是对 array 的 view, 意味着 slice 知道底层的
array, 因此 s2 仍然能取出底层 array 对应的值.
我们称上述特性为 slice 的可拓展性.
*/
/* slice 的底层实现由 ptr/len/cap 构成
- ptr 指向 slice 第一个元素的地址;
- len 指向 slice 的长度;
- cap 为 capacity 的缩写, 表明 slice 至多可以向后扩展的长度
注意: slice 不可以向前拓展.
具体的扩展方法: s[i] 不可以超越 len(s), 向后扩展不能超过 cap(s) */
fmt.Printf("s1=%v, len(s1)=%d, cap(s1)=%d\n", s1, len(s1), cap(s1))
fmt.Printf("s1=%v, len(s2)=%d, cap(s2)=%d\n", s2, len(s2), cap(s2))
}
注意到,可以使用 len 来获取当前切片当中存储元素的长度。可以使用 cap 得到当前切片的最大容量,如果当前切片与某个数组绑定(即,切片为数组的视图),那么从切片的起始位置开始(比如第二个位置)到数组的最大位置(比如数组为{0, 1, 2, 3, 4, 5, 6,7 }
,切片为{2, 3, 4, 5}
)是切片的 cap(2, 3, 4, 5, 6, 7
),切片可以取 cap 内的值,正如上例所示。
我个人认为这不是一个很好的特性,在给定视图的基础上取视图以外的元素意义不大。
切片的操作
切片的操作可以归纳为三种,分别是创建、拷贝和删除。
我们可以创建一个空的切片,它可以不是任何数组的视图,此时的切片类似于 C++ 当中的 vector:
切片的创建
var s []int // 将 s 定义为一个 slice
for i := 0; i < 100; i++ {
printSlice(s) // 打印切片的相关信息
s = append(s, 2*i+1) // 与 vector 类似, 切片的 cap(s) 同样会在需要时翻倍增长
}
可以使用内置的 make 函数创建一个指定长度(len,实际上初始的 cap 也可以指定)的空切片:
s1 := []int{2, 4, 6, 8}
printSlice(s1)
s2 := make([]int, 16) // 我们希望构建一个长度为 16 的 slice
// 👆 但是目前我们还不知道 slice 当中的值, 此时需要使用内建的函数 make
s3 := make([]int, 10, 32) // 32 是 cap
printSlice(s2) // len = 16, cap = 32
printSlice(s3) // len = 10, cap = 32
切片的拷贝
可以使用内建的 copy 函数来完成切片的拷贝工作。‘
fmt.Println("Copying slice:")
copy(s2, s1) // 系统内建的 copy 函数, 首先是 dst, 之后是 src
printSlice(s2) // 将 s1 的 2, 4, 6, 8 拷贝到了 s2 中
切片当中首尾元素的删除
可以使用赋值的方式来完成首尾元素的删除:
fmt.Println("Deleting elements from slice")
s2 = append(s2[:3], s2[4:]...)
printSlice(s2)
fmt.Println("Popping from front")
front := s2[0]
s2 = s2[1:]
fmt.Println("Popping from back")
tail := s2[len(s2)-1]
s2 = s2[:len(s2)-1]
fmt.Println(front, tail)
printSlice(s2)
上述内容的完整代码如下:
package main
import "fmt"
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
// 可以看到 slice 的底层扩容原理与 C++ 的 vector 非常相似
// 当 cap 不足时, 将 cap 翻倍
}
func main() {
fmt.Println("Creating Slice:")
var s []int // 将 s 定义为一个 slice
// 根据前面的学习内容, 我们知道 slice 是对 array 的 view
// 但是现在没有 array 的定义, 或者说当前定义的 slice 没有和 array 绑定
// 我推测可以直接对 slice 进行操作, 它将会自动分配一个 array
// 正如前面 append 操作中所看到的, 当 array 达到 cap 时, 系统会分配新的 array
// Zero value for slice is nil
for i := 0; i < 100; i++ {
printSlice(s)
s = append(s, 2*i+1)
}
s1 := []int{2, 4, 6, 8}
printSlice(s1)
s2 := make([]int, 16) // 我们希望构建一个长度为 16 的 slice
// 👆 但是目前我们还不知道 slice 当中的值, 此时需要使用内建的函数 make
s3 := make([]int, 10, 32) // 32 是 cap
printSlice(s2) // len = 16, cap = 32
printSlice(s3) // len = 10, cap = 32
fmt.Println("Copying slice:")
copy(s2, s1) // 系统内建的 copy 函数, 首先是 dst, 之后是 src
printSlice(s2) // 将 s1 的 2, 4, 6, 8 拷贝到了 s2 中
fmt.Println("Deleting elements from slice")
s2 = append(s2[:3], s2[4:]...)
printSlice(s2)
fmt.Println("Popping from front")
front := s2[0]
s2 = s2[1:]
fmt.Println("Popping from back")
tail := s2[len(s2)-1]
s2 = s2[:len(s2)-1]
fmt.Println(front, tail)
printSlice(s2)
}