Go 切片
Go - Slices (切片)
Section titled “Go - Slices (切片)”Slices (切片) 是 Go 语言中最强大和常用的数据结构之一。与数组相比,它们为数据序列提供了一种更灵活、更动态的接口。
可以将 slice 想象成底层数组上的一个视图或窗口。与数组不同的是,slice 的大小是动态的——它们可以增长和收缩。
Slice 的内部结构
Section titled “Slice 的内部结构”slice 本身并不直接存储数据。它是一个包含三个字段的小型描述符结构:
理解这种内部结构是理解 slice 行为的关键,尤其是在对现有 slice 进行切片操作或使用 append 函数时。
创建 Slice
Section titled “创建 Slice”有几种创建 slice 的方法:
- Slice 字面量: 类似于数组字面量,但不指定大小。
// 创建一个底层数组,并返回一个引用该数组的 sliceprimes := []int{2, 3, 5, 7, 11, 13} // len=6, cap=6
names := []string{"Go", "Python", "Java"} // len=3, cap=3
emptySlice := []int{} // len=0, cap=0 (但不是 nil)- 使用
make函数: 创建一个 slice,同时分配指定大小的底层数组。元素会被初始化为其零值。
// make([]T, 长度, 容量)numbers := make([]int, 5) // len=5, cap=5。初始化为 [0 0 0 0 0]
// 显式指定长度和容量data := make([]byte, 3, 10) // len=3, cap=10。初始化为 [0 0 0]当你知道大概需要的尺寸时,使用 make 很有用,有助于在追加(append)时避免多次重新分配。
- 对现有数组或 Slice 进行切片: 创建一个新 slice,它引用原数组或 slice 的一部分。
baseArray := [6]int{0, 1, 2, 3, 4, 5}
sliceA := baseArray[1:4] // 从索引 1(包含)到 4(不包含)创建 slice // sliceA 包含 [1 2 3],len=3, cap=5 (从索引 1 到 baseArray 的末尾)
sliceB := sliceA[0:2] // 从 sliceA 创建 slice,索引 0 到 2 // sliceB 包含 [1 2],len=2, cap=5 (仍然共享 baseArray 的容量)
// sliceA 和 sliceB 共享同一个底层数组 (baseArray)!// 修改 sliceB[0] 也会改变 sliceA[0] 和 baseArray[1]。- 声明 Nil Slice: 声明一个 slice 变量而不进行初始化会导致一个 nil slice (空切片)。
var nilSlice []int // nilSlice 是 nil,len=0, cap=0
fmt.Println(nilSlice == nil) // 输出: truenil slice 作为初始值很有用;许多标准库函数都能优雅地处理它们(例如,append)。
长度 (len) 和 容量 (cap)
Section titled “长度 (len) 和 容量 (cap)”内置的 len() 函数返回 slice 中的元素数量(即其长度)。
内置的 cap() 函数返回 slice 的容量(在需要重新分配之前它可以达到的最大长度)。
package main
import "fmt"
func main() { s := make([]int, 3, 5) // 长度 3, 容量 5 printSliceInfo("s", s)
s = s[:cap(s)] // 将长度扩展到容量 printSliceInfo("s extended", s)
s = s[1:] // 从开头缩短长度 printSliceInfo("s sliced", s)}
func printSliceInfo(name string, x []int) { fmt.Printf("%s: len=%d cap=%d slice=%v\n", name, len(x), cap(x), x)}输出:
s: len=3 cap=5 slice=[0 0 0]s extended: len=5 cap=5 slice=[0 0 0 0 0]s sliced: len=4 cap=4 slice=[0 0 0 0]子切片操作 (slice[low:high])
Section titled “子切片操作 (slice[low:high])”切片操作会创建一个新的 slice header,它指向同一个底层数组。语法:slice[low:high]
low:起始索引(包含)。如果省略,默认为 0(即 [:high])。
high:结束索引(不包含)。如果省略,默认为 len(slice)(即 [low:])。
新 slice 的长度是 high - low。容量是根据原 slice 的 low 索引计算到其底层数组容量的末尾。
package main
import "fmt"
func main() { numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8} // len=9, cap=9 printSliceInfo("numbers", numbers)
// 从索引 1(包含)到 4(不包含)的子切片 fmt.Println("numbers[1:4] ==", numbers[1:4]) // [1 2 3] printSliceInfo("numbers[1:4]", numbers[1:4]) // len=3 cap=8
// 省略下界意味着 0 fmt.Println("numbers[:3] ==", numbers[:3]) // [0 1 2] printSliceInfo("numbers[:3]", numbers[:3]) // len=3 cap=9
// 省略上界意味着 len(s) fmt.Println("numbers[4:] ==", numbers[4:]) // [4 5 6 7 8] printSliceInfo("numbers[4:]", numbers[4:]) // len=5 cap=5
// IMPORTANT: 通过子切片进行的修改会影响原始 slice! sub := numbers[2:5] // [2 3 4],len=3, cap=7 sub[0] = 99 fmt.Println("修改 sub[0] 后:") printSliceInfo("sub", sub) // sub: len=3 cap=7 slice=[99 3 4] printSliceInfo("numbers", numbers) // numbers: len=9 cap=9 slice=[0 1 99 3 4 5 6 7 8]}
func printSliceInfo(name string, x []int) { fmt.Printf("%s: len=%d cap=%d slice=%v\n", name, len(x), cap(x), x)}输出:
numbers: len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]numbers[1:4] == [1 2 3]numbers[1:4]: len=3 cap=8 slice=[1 2 3]numbers[:3] == [0 1 2]numbers[:3]: len=3 cap=9 slice=[0 1 2]numbers[4:] == [4 5 6 7 8]numbers[4:]: len=5 cap=5 slice=[4 5 6 7 8]After modifying sub[0]:sub: len=3 cap=7 slice=[99 3 4]numbers: len=9 cap=9 slice=[0 1 99 3 4 5 6 7 8]向 Slice 追加元素 (append)
Section titled “向 Slice 追加元素 (append)”内置的 append 函数将元素添加到 slice 的末尾。理解它如何处理容量至关重要:
至关重要的是,你必须始终将 append 的结果重新赋值给 slice 变量,因为它可能返回一个指向不同底层数组的新 slice。
slice = append(slice, element1, element2, ...)slice = append(slice, anotherSlice...)package main
import "fmt"
func main() { var numbers []int // 从一个 nil slice 开始 printSliceInfo("numbers (initial)", numbers)
// append 适用于 nil slice numbers = append(numbers, 0) printSliceInfo("numbers (appended 0)", numbers)
// 添加一个元素 numbers = append(numbers, 1) printSliceInfo("numbers (appended 1)", numbers)
// 一次添加多个元素 // 如果超出容量,这可能会触发重新分配 numbers = append(numbers, 2, 3, 4) printSliceInfo("numbers (appended 2,3,4)", numbers)
// 使用 ... 追加另一个 slice moreNumbers := []int{5, 6} numbers = append(numbers, moreNumbers...) printSliceInfo("numbers (appended moreNumbers)", numbers)}
func printSliceInfo(name string, x []int) { fmt.Printf("%s: len=%d cap=%d slice=%v\n", name, len(x), cap(x), x)}输出(容量可能因 Go 版本增长策略而略有不同):
numbers (initial): len=0 cap=0 slice=[]numbers (appended 0): len=1 cap=1 slice=[0]numbers (appended 1): len=2 cap=2 slice=[0 1]numbers (appended 2,3,4): len=5 cap=6 slice=[0 1 2 3 4]numbers (appended moreNumbers): len=7 cap=12 slice=[0 1 2 3 4 5 6]复制 Slice (copy)
Section titled “复制 Slice (copy)”内置的 copy 函数将元素从源 slice (src) 复制到目标 slice (dst)。它返回复制的元素数量。
numCopied := copy(dst, src)copy 函数会复制 min(len(dst), len(src)) 个元素。它逐个元素地从源复制到目标,并且不会自动增长目标 slice。源和目标 slice 可以重叠。
package main
import "fmt"
func main() { source := []int{1, 2, 3, 4} dest1 := make([]int, 2) // 目标 slice 小于源 slice num1 := copy(dest1, source) fmt.Printf("Copied %d elements to dest1: %v\n", num1, dest1) // 输出: Copied 2 elements to dest1: [1 2]
dest2 := make([]int, 5) // 目标 slice 大于源 slice num2 := copy(dest2, source) fmt.Printf("Copied %d elements to dest2: %v\n", num2, dest2) // 输出: Copied 4 elements to dest2: [1 2 3 4 0]
// 要创建一个独立的副本,需要创建一个与源 slice 大小相同的新 slice independentCopy := make([]int, len(source)) copy(independentCopy, source) independentCopy[0] = 99 // 修改副本 fmt.Println("Source:", source) // 输出: Source: [1 2 3 4] fmt.Println("Independent Copy:", independentCopy) // 输出: Independent Copy: [99 2 3 4]}Slice 是地道的 Go 编程中非常基础的概念,它在管理数据序列时提供了性能和灵活性的良好平衡。