Go 语言中 slice 是一种构建在 array 数组类型上的抽象类型,所以不管是从 array、slice 或者使用 make 构建 slice ,每个 slice 都是其底层数组的一个映射。一个 slice 由三部分组成,分别为 ptr(pointer)指向底层数组的指针、len(length)长度、cap(capacity)容量,下面来详细说明这三个部分。
ptr:既然 ptr 是指向底层数组的指针,那么指向数组的什么位置呢?创建 slice 时需要指定一个左闭右开(包含左边界,不包含右边界)的区间,slice 创建之后,观察代码中 L9 输出以及上图,不难发现 ptr 会指向区间左边界对应的数组元素位置。
package main
import "fmt"
func main() {
array := [...]int{ 0, 1, 2, 3, 4, 5, 6, 7 }
sliceA := array[1:6]
fmt.Printf("sliceA addr: %p, array[1] addr: %p;\n", sliceA, &array[1])
}
// L9: sliceA addr: 0xc00018a008, array[1] addr: 0xc00018a008;
len:与 array 相同,len 代表 slice 当前元素的数量,如果 slice 声明为 slice := array[n:m],那么 len 就等于 m - n 。另外一点与 array 相同的时,slice 也可以使用 index 来访问内容,但也要注意,如果 index < 0 或者 >= len(slice),也会出现越界。
cap:slice 的 len 可以扩容到到的最大值,值为 ptr 指向的 array 元素位置到 array 末尾的元素数量,如果 prt 指向 array 第 n 个元素,那么 cap = len(array) - n 。
package main
import "fmt"
func main() {
array := [...]int{ 0, 1, 2, 3, 4, 5, 6, 7 }
sliceA := array[1:6]
fmt.Printf("sliceA addr: %p, array[1] addr: %p;\n", sliceA, &array[1])
fmt.Printf("sliceA: %d, len: %d, cap: %d;\n", sliceA, len(sliceA), cap(sliceA))
}
// L9: sliceA addr: 0xc00018a008, array[1] addr: 0xc00018a008;
// L10: sliceA: [1 2 3 4 5], len: 5, cap: 7;
需要注意的是,如果从一个 sliceA 构建 sliceB,那么 sliceB 的 ptr 也会指向 sliceA 的 ptr 指向的 array。因为 slice 本质上只是一个指向 array 的 pointer,所以理论上基于一个 array 可以构建任意多个 slice。
如上图所示,虽然一个 array 上可以构建多个 slice,但是也正因为这些 slice 共用一个底层 array,并且 slice 修改时会修改底层 array,所以在这个底层 array 之上,其它映射了相同区间的 slice 也会受到影响。
package main
import "fmt"
func main() {
array := [...]int{ 0, 1, 2, 3, 4, 5, 6, 7 }
sliceA := array[1:6]
fmt.Printf("sliceA addr: %p, array[1] addr: %p;\n", sliceA, &array[1])
fmt.Printf("sliceA: %d, len: %d, cap: %d;\n", sliceA, len(sliceA), cap(sliceA))
sliceB := sliceA[:cap(sliceA)]
fmt.Printf("sliceB: %d, len: %d, cap: %d;\n", sliceB, len(sliceB), cap(sliceB))
sliceC := sliceB[2:4]
fmt.Printf("sliceC: %d, len: %d, cap: %d;\n", sliceC, len(sliceC), cap(sliceC))
sliceC[1] = 100
fmt.Printf("sliceC: %d, len: %d, cap: %d;\n", sliceC, len(sliceC), cap(sliceC))
fmt.Printf("array: %d, len: %d, cap: %d;\n", array, len(array), cap(array))
fmt.Printf("sliceA: %d, len: %d, cap: %d;\n", sliceA, len(sliceA), cap(sliceA))
fmt.Printf("sliceB: %d, len: %d, cap: %d;\n", sliceB, len(sliceB), cap(sliceB))
}
// L9: sliceA addr: 0xc000102008, array[1] addr: 0xc000102008;
// L10: sliceA: [1 2 3 4 5], len: 5, cap: 7;
// L14: sliceB: [1 2 3 4 5 6 7], len: 7, cap: 7;
// L18: sliceC: [3 4], len: 2, cap: 5;
// L22: sliceC: [3 100], len: 2, cap: 5;
// L23: array: [0 1 2 3 100 5 6 7], len: 8, cap: 8;
// L24: sliceA: [1 2 3 100 5], len: 5, cap: 7;
// L25: sliceB: [1 2 3 100 5 6 7], len: 7, cap: 7;