Golang切片操作详解 (2026年03月27日) 切片(Slice)简介 Go语言的切片是对数组的抽象,提供了灵活、强大的序列操作能力。切片是引用类型,不存储实际数据,而是引用底层数组的一个连续区间。切片的长度可以动态变化,这使得它比数组更常用。 切片基础 切片定义 切片底层结构 切片操作 访问和修改元素 切片截取 添加元素 删除元素 复制切片 切片排序 切片扩容机制 容量增长策略 扩容规则(Go 1.
Go语言的切片是对数组的抽象,提供了灵活、强大的序列操作能力。切片是引用类型,不存储实际数据,而是引用底层数组的一个连续区间。切片的长度可以动态变化,这使得它比数组更常用。
// 声明切片(var声明,值为nil) var s1 []int // 使用make创建切片 s2 := make([]int, 5) // 长度为5的切片,元素初始化为零值 s3 := make([]int, 3, 10) // 长度3,容量10 // 使用字面量创建切片 s4 := []int{1, 2, 3, 4, 5} s5 := []string{"a", "b", "c"} // 创建空切片 s6 := []int{} // 空切片,不为nil var s7 []int // nil切片
type slice struct { ptr unsafe.Pointer // 指向底层数组的指针 len int // 切片长度 cap int // 切片容量 }
s := []int{10, 20, 30, 40, 50} // 访问元素(索引从0开始) fmt.Println(s[0]) // 输出:10 fmt.Println(s[2]) // 输出:30 // 修改元素 s[1] = 99 fmt.Println(s) // 输出:[10 99 30 40 50] // 遍历切片 for i, v := range s { fmt.Printf("index=%d, value=%d\n", i, v) } // 只要索引 for i := range s { fmt.Println(i) } // 只要值 for _, v := range s { fmt.Println(v) }
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // 切片操作:s[low:high],包含low,不包含high s1 := s[1:4] // [1 2 3] s2 := s[:5] // [0 1 2 3 4] s3 := s[5:] // [5 6 7 8 9] s4 := s[:] // [0 1 2 3 4 5 6 7 8 9] // 注意:切片截取共享底层数组 s[0] = 999 fmt.Println(s1) // 输出:[999 2 3],s1是s的视图,会看到修改
// append添加元素到末尾 s := []int{1, 2, 3} s = append(s, 4) // [1 2 3 4] s = append(s, 5, 6, 7) // [1 2 3 4 5 6 7] // 合并两个切片(使用...展开) s1 := []int{1, 2} s2 := []int{3, 4} s1 = append(s1, s2...) // [1 2 3 4] // 注意:append可能重新分配底层数组 s3 := make([]int, 3, 5) // len=3, cap=5 fmt.Println(len(s3), cap(s3)) // 3 5 s3 = append(s3, 1, 2) // 未超过容量 fmt.Println(len(s3), cap(s3)) // 5 5 s3 = append(s3, 3) // 超过容量,重新分配 fmt.Println(len(s3), cap(s3)) // 6 10(容量翻倍)
// 删除索引i的元素 func deleteSlice(s []int, i int) []int { return append(s[:i], s[i+1:]...) } s := []int{0, 1, 2, 3, 4} s = deleteSlice(s, 2) // 删除索引2的元素 fmt.Println(s) // [0 1 3 4] // 删除多个元素 s = append(s[:1], s[3:]...) // 删除索引1-2的元素 // 清空切片 s = s[:0] // len=0, cap不变
// copy复制元素 src := []int{1, 2, 3, 4, 5} dst := make([]int, len(src)) n := copy(dst, src) fmt.Println(n) // 5(复制的元素个数) fmt.Println(dst) // [1 2 3 4 5] // 复制部分元素 dst2 := make([]int, 3) copy(dst2, src) // 只复制3个元素 fmt.Println(dst2) // [1 2 3] // 直接赋值是引用 s1 := []int{1, 2, 3} s2 := s1 // s1和s2指向同一底层数组 s2[0] = 999 fmt.Println(s1) // [999 2 3] // 使用copy深拷贝 s3 := make([]int, len(s1)) copy(s3, s1) s3[0] = 888 fmt.Println(s1) // [999 2 3](不受影响)
import "sort" s := []int{5, 2, 6, 3, 1, 4} sort.Ints(s) // 升序:[1 2 3 4 5 6] sort.Sort(sort.Reverse(sort.IntSlice(s))) // 降序:[6 5 4 3 2 1] // 字符串切片排序 names := []string{"Alice", "Bob", "Charlie"} sort.Strings(names) // [Alice Bob Charlie] // 自定义排序 type Person struct { Name string Age int } people := []Person{ {"Alice", 25}, {"Bob", 30}, {"Charlie", 20}, } // 按年龄排序 sort.Slice(people, func(i, j int) bool { return people[i].Age < people[j].Age }) fmt.Println(people) // [{Charlie 20} {Alice 25} {Bob 30}]
s := make([]int, 0, 1) fmt.Println(len(s), cap(s)) // 0 1 for i := 0; i < 20; i++ { s = append(s, i) fmt.Printf("len=%d, cap=%d\n", len(s), cap(s)) } // 输出(Go 1.18+): // len=1, cap=1 // len=2, cap=2 // len=3, cap=4 // len=4, cap=4 // len=5, cap=8 // len=6, cap=8 // len=7, cap=8 // len=8, cap=8 // len=9, cap=16 // ...
扩容规则(Go 1.18+):
// 使用切片实现栈 stack := []int{} // Push stack = append(stack, 1, 2, 3) // [1 2 3] // Pop top := stack[len(stack)-1] // 获取栈顶元素 stack = stack[:len(stack)-1] // 弹出 // Peek if len(stack) > 0 { top := stack[len(stack)-1] }
// 使用切片实现队列(效率不高,使用channel更好) queue := []int{1, 2, 3} // Enqueue queue = append(queue, 4) // [1 2 3 4] // Dequeue front := queue[0] queue = queue[1:] // [2 3 4]
s := []int{1, 2, 3, 4, 5, 6} // 保留偶数 var evens []int for _, v := range s { if v%2 == 0 { evens = append(evens, v) } } fmt.Println(evens) // [2 4 6] // 原地过滤(避免创建新切片) n := 0 for _, v := range s { if v%2 == 0 { s[n] = v n++ } } s = s[:n] // [2 4 6]
// 创建二维切片 matrix := make([][]int, 3) for i := range matrix { matrix[i] = make([]int, 4) } fmt.Println(matrix) // [[0 0 0 0] [0 0 0 0] [0 0 0 0]] // 使用字面量 matrix2 := [][]int{ {1, 2, 3}, {4, 5, 6}, {7, 8, 9}, } // 遍历二维切片 for i, row := range matrix2 { for j, val := range row { fmt.Printf("[%d][%d]=%d ", i, j, val) } fmt.Println() }
// 线性查找 func findSlice(s []int, target int) int { for i, v := range s { if v == target { return i } } return -1 // 未找到 } s := []int{10, 20, 30, 40, 50} idx := findSlice(s, 30) // 2
预分配容量:如果知道最终大小,使用make预分配
s := make([]int, 0, 1000) // 避免多次扩容
避免内存泄漏:大切片的子切片会保留整个底层数组
// 问题代码 s := make([]byte, 1<<20) // 1MB s1 := s[:100] // s1仍持有1MB底层数组的引用 // 解决方法:复制需要的部分 s2 := make([]byte, 100) copy(s2, s1)
清空切片但保留容量
s = s[:0] // len=0, cap不变
使用copy代替append + 删除
// 低效 s = append(s[:i], s[i+1:]...) // 高效(元素较少时) copy(s[i:], s[i+1:]) s = s[:len(s)-1]
| 特性 | 切片 | 数组 |
|---|---|---|
| 长度 | 动态可变 | 固定 |
| 类型 | []T |
[N]T |
| 比较 | 不能用==比较(除nil外) | 可以用==比较 |
| 传参 | 引用传递(拷贝切片头) | 值传递(拷贝全部元素) |
| 使用频率 | 高(绝大多数场景) | 低(特定场景) |
s[:0]:保留容量便于重用Go语言的切片设计简洁而强大,掌握其特性和陷阱是编写高效Go代码的基础。