Slices 表示变长序列,其元素类型相同。slice 类型写作 []T,元素类型为 T。
Slices 用于访问其 underlying array 元素的子序列。Slice 有 3 个部分:pointer,length,capacity:
- pointer 指向 slice 能够访问的数组部分的第一个元素,它不必是数组的第一个元素。
- length 是 slice 元素的数量,通过 len() 返回。
- capacity 是从 slice 的第一个元素开始到底层数组的最后一个元素之间的元素数量,通过 cap() 返回。
多个 slice 可能共享同一个 underlying array,也可能引用该底层数组的重叠部分。下面声明一个数组,和它的 slice:
months := [...]string{1: "January", /* ... */, 12: "December"}
Q2 := months[4:7]
summer := months[6:9]
fmt.Println(Q2) // ["April" "May" "June"]
fmt.Println(summer) // ["June" "July" "August"]
slice operator s[i:j],其中 0 <= i <= j <= cap(s),引用序列 s 从 i 到 j(不包含) 之间的元素,s 可能是数组变量,指向数组的指针,或另一个 slice。i 缺省为 0,j 缺省为 len(s)。
slicing 超过 cap(s) 造成 panic,但是 slicing 超过 len(s) 会拓展 slice:
fmt.Println(summer[:20]) // panic: out of range
endlessSummer := summer[:5] // extend a slice (within capacity)
fmt.Println(endlessSummer) // "[June July August September October]"
因为 slice 包含指向数组元素的指针,向函数传递 slice 允许函数修改底层数组元素。换句话说,拷贝一个 slice 创建一个底层数组的 alias。
gopl.io/che/rev
// reverse reverses a slice of ints in place
func reverse(s []int) {
for i, j := 0, len(s) - 1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}
a:= [...]int{0, 1, 2, 3, 4, 5} // array initializes
reverse(a[:])
fmt.Println(a) // "[5 4 5 2 1 0]"
s := []int{0, 1, 2, 3, 4, 5} // slice initializes
array literal 使用数组长度,slice literal 不使用 slice 长度。slice literal 隐式地创建一个正确尺寸的 array 并产生一个指向它的 slice。
slice 是不能进行比较的,我们不能使用 == 来测试两个 slice 是否包含相同的元素。[]byte 类型的 slice 可以使用 bytes.Equal 函数进行比较,其他类型必须自己比较:
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
}
为什么 slice 不能进行比较:
- slice 中的元素是间接的,它可能包含自己,这样比较可能是不会终止的。
- 一个固定的 slice 可能因为 uderlying array 内容的改变而变化,
不同的时刻包含不同的元素
。因为 hash table ,比如 Go 中的 map 类型,仅对它的键做浅拷贝
,它需要每个键在 map 的整个生命周期中
保持相同的相等性
。Deep equivalence 因此使 slices 不适合用作 map 的 keys。对于 reference type 比如 pointer 和 channels,操作符 == 测试 reference identity(引用恒等式),即,两个实体是否引用相同的东西。一个模糊的 “shallow” 相等性测试对 slices 可能是有用的,它也能解决 map 的问题,但是对 slice 和 array 不一致的 == 操作符处理方式将造成困扰。最安全的选择是完全不允许 slice 间进行比较。
slice 唯一合法的比较是与 nil 进行比较:
if silce == nil { /* ... */ }
slice 的 zero value 是 nil。一个 nil slice 没有 underlying array。nil slice 的 length 和 capacity 为 0,但是 non-nil 的 slice 的 length 和 capacity 也可能是 0,比如 []int{} 或 make([]int, 3)[3:]。和任何拥有 nil 值的类型一样,特定 slice 类型的 nil 值可以使用转换表达式得到,例如 []int(nil)。
var s []int // len(s) == 0, s == nil
s = nil // len(s) == 0, s == nil
s = []int(nil) // len(s) == 0, s == nil
s = []int{} // len(s) == 0, s != nil
使用 len(s) == 0 测试一个 slice 是否 empty。除了与 nil 进行比较,nil slice 的行为和其他 zero-length 的 slice 一致。
使用 make 创建一个 slice,指定 slice 的类型,length,可选的指定 capacity:
make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[:len]
实际上,make 创建一个 unnamed array 变量,并返回这个 array 的一个 slice。这个 array 只能通过返回的 slice 进行访问。
The append Function
内置的 append 函数向 slices 追加 items:
var runes []rune
for _, r := range "Hello, 世界" {
runes = append(runes, r)
}
fmt.Printf("%q\n", runes) // "['H' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']"
等价于:
runes := []rune("Hello, 世界")
gopl.io/ch4/append
func appendInt(x []int, y int) []int {
var z []int
zlen := len(x) + 1
if zlen <= cap(x) {
zlen = x[:zlen]
} else {
// There is insufficient space. Allocate a new array.
// Grow by doubling, for amortized linear complexity.
zcap := zlen
if zcap < 2*len(x) {
zcap = 2*len(x)
}
z = make([]int, zlen, zcap)
copy(z, x) // a built-in function; see text
}
z[len(x)] = y
return z
}
通常我们不知道调用 append 是否会造成一次 reallocation。我们也不能假设在旧的 slice 的元素上进行的操作会反映到新的 slice 上。结果,通常将 append 的调用返回的结果赋值给传递给 append 的那个 slice 变量:
runes = append(runes, r)
更新 slice 变量
不仅仅是在调用 append 是需要,对任何可能改变 slice 的 length 或 capacity
或使 slice 引用不同的 underlying array
的函数来说都是如此。
slice 并非 “pure” 引用类型,而是像类似于这个 struct 的聚合类型:
type IntSlice struct {
ptr *int
len, cap int
}
append 可以向 slice 追加单个元素,多个元素,甚至整个 slice:
var x = []int
x = append(x, 1)
x = append(x, 2, 4)
x = append(x, 4, 5, 6)
x = append(x, x...) // append the slice x
fmt.Println(x) // "[1 2 3 4 5 6 1 2 3 4 5 6]"
func appednInt(x []int, y ...int) []int {
var z []int
zlen := len(x) + len(y)
// ...expend z to at least zlen...
copy(z[len(x):], y)
return z
}
appendInt 声明中的省略号 “…” 使函数 variadic :它接受任意数量的最终参数
。上述 append 的调用中对应的省略号展现了如果应用来自 slice 的参数列表
。
In-Place Slice Techniques
gopl.io/ch4/nonempty
// Nonempty is an example of an in-place slice algorithm
package main
import "fmt"
// nonempty returns a slice holding only the non-empty strings.
// The underlying array is modified during the call.
func nonempty(strings []string) []string {
i := 0
for _,s := range strings {
if s != "" {
strings[i] = s
i++
}
}
return strings[:i]
}
data := []stirng{"one", "", "three"}
fmt.Printf("%q\n", nonempty(data)) // `["one" "three"]`
fmt.Printf("%q\n", data) // `["one" "three" "three"]`
通常我们写作:
data = nonempty(data)
nonempty 函数也可以使用 append 函数重写:
func nonempty2(strings []string) []string {
out := strings[:0] // zero-length slice of original
for _, str := range strings {
if str != "" {
out = append(out, str)
}
}
return out
}
以这种方式重用 array 需要:一个输入值至多产生一个输出值,比如对一个序列进行过滤或者将邻近的元素结合。在某些场景下,这种技术十分清晰,高效,有用。
slice 可以用于实现一个 stack:
stack = append(stack, v) // push v
top := stack[len(stack) - 1] // top of stack
stack = stack[:len(stack) - 1] // pop
删除 slice 中的一个元素。保持其他元素顺序不变:
func remove(seq []T, n int) ([]T, error) {
if n > len(seq) {
var error err
return nil, err
}
out= seq[:n+1]
out = out.append(seq[n+1:])
return out, nil
}
func remove(slice []int, i int) []int {
copy(slice[i:], slice[i+1:])
return slice[:len(slice)-1]
}
func main() {
s := []int{5, 6, 7, 8, 9}
fmt.Println(remove(s, 2)) // "[5 6 8 9]"
}