Golang - 2.切片
Go 切片
语法规则
Go 切片表达式 a[low:high] 遵循 左闭右开 原则:
| 参数 | 含义 | 是否包含 |
|---|---|---|
| low | 起始索引 | 包含 |
| high | 结束索引 | 不包含 |
代码示例
s1 := [5]int{1, 2, 3, 4, 5}
s2 := s1[1:3]
fmt.Println(s2) // [2 3]
数组: [1, 2, 3, 4, 5]
索引: 0 1 2 3 4
↑ ↑
low high (不包含)
- 为什么这样设计
- 左闭右开 [low, high) 的好处:
- 长度计算简单:len = high - low
- 连续切片不重叠:a[0:2] 和 a[2:5] 可以无缝拼接
Go 切片(Slice)详解
1. 切片是什么
切片是 Go 语言中动态大小的、灵活的数组视图。它是对底层数组的引用,本身不存储数据,而是描述数组的一段区间。
切片 vs 数组
| 特性 | 数组(Array) | 切片(Slice) |
|---|---|---|
| 长度 | 固定,是类型的一部分 | 动态,可变化 |
| 传递 | 值传递(复制整个数组) | 引用传递(底层共享数据) |
| 声明 | [5]int |
[]int |
| 灵活性 | 低 | 高 |
arr := [5]int{1, 2, 3, 4, 5} // 数组
sli := []int{1, 2, 3, 4, 5} // 切片
2. 切片的底层结构
切片在内存中是一个结构体(slice header),包含三个字段:
+-----------+---------+---------+
| 指针(ptr) | 长度(len) | 容量(cap) |
+-----------+---------+---------+
| 指向底层数组 | 元素个数 | 可用容量 |
+-----------+------------------+
// 伪代码表示
struct SliceHeader {
Data uintptr // 指向底层数组的指针
Len int // 切片当前长度(元素个数)
Cap int // 从指针位置到底层数组末尾的总容量
}
3. 创建切片的四种方式
方式一:字面量声明
s1 := []int{1, 2, 3, 4, 5}
fmt.Println(s1) // [1 2 3 4 5]
方式二:从数组切片
arr := [5]int{1, 2, 3, 4, 5}
s2 := arr[1:4] // 左闭右开 [1, 4)
fmt.Println(s2) // [2 3 4]
方式三:使用 make
// make([]T, length, capacity)
s3 := make([]int, 5, 10) // 长度5,容量10,初始值全为0
fmt.Println(len(s3)) // 5
fmt.Println(cap(s3)) // 10
方式四:从切片切片
s4 := []int{1, 2, 3, 4, 5, 6, 7}
s5 := s4[2:5]
fmt.Println(s5) // [3 4 5]
4. 切片表达式深度解析
基本语法
a[low : high]
low:起始索引,包含(默认值 0)high:结束索引,不包含(默认值 len(a))
arr := [5]int{10, 20, 30, 40, 50}
arr[1:3] // [20 30] low=1, high=3
arr[:3] // [10 20 30] low=0(默认), high=3
arr[2:] // [30 40 50] low=2, high=5(默认)
arr[:] // [10 20 30 40 50] low=0, high=5
为什么采用左闭右开?
数组: [10, 20, 30, 40, 50]
索引: 0 1 2 3 4
↑ ↑
low=1 high=3 (不包含)
结果: [20, 30]
优点:
1. 长度计算直观:len = high - low(3 - 1 = 2)
2. 连续切片无重叠:arr[0:2] + arr[2:5] 完整覆盖无遗漏
5. 切片是引用类型
切片操作不会复制数据,而是共享底层数组。
arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:4] // s 指向 arr 的同一内存
s[0] = 999
fmt.Println(arr) // [1 999 3 4 5] 原数组被修改!
fmt.Println(s) // [999 3 4]
内存示意图
arr: [1, 999, 3, 4, 5]
↑ ↑ ↑
0 1 4
│
s: [999, 3, 4]
len=3, cap=4
6. 容量(Cap)与长度(Len)
arr := [5]int{10, 20, 30, 40, 50}
s := arr[1:3]
fmt.Println(s) // [20 30]
fmt.Println(len(s)) // 2 (high - low = 3 - 1)
fmt.Println(cap(s)) // 4 (从索引1到数组末尾 = 5 - 1)
容量计算
cap = 底层数组长度 - low
arr := [6]int{0, 1, 2, 3, 4, 5}
s1 := arr[1:4] // cap = 6 - 1 = 5
s2 := arr[2:3] // cap = 6 - 2 = 4
7. append 与扩容机制
基本用法
s := []int{1, 2, 3}
s = append(s, 4, 5)
fmt.Println(s) // [1 2 3 4 5]
扩容规则
当 append 超出容量时,Go 会分配新数组:
arr := [4]int{1, 2, 3, 4}
s := arr[:2] // [1, 2], len=2, cap=4
s = append(s, 5, 6) // 未超cap,仍指向原数组
fmt.Println(arr) // [1 2 5 6] 原数组被改!
s = append(s, 7, 8, 9) // 超cap,分配新数组
fmt.Println(arr) // [1 2 5 6] 原数组不变
扩容策略(Go 1.18+)
| 原容量 | 新容量策略 |
|---|---|
| < 256 | 翻倍(2x) |
| >= 256 | 约 1.25x ~ 1.5x,平滑过渡 |
8. copy 函数
用于复制切片数据,避免共享底层数组。
src := []int{1, 2, 3, 4, 5}
dst := make([]int, len(src))
copy(dst, src)
dst[0] = 999
fmt.Println(src) // [1 2 3 4 5] 原切片不受影响
fmt.Println(dst) // [999 2 3 4 5]
copy 返回实际复制的元素个数(取两个切片长度的最小值)。
9. 常见坑点
坑 1:切片导致原数组意外修改
func getSlice() []int {
arr := [5]int{1, 2, 3, 4, 5}
return arr[:3] // 返回后 arr 仍在堆上,外部可改
}
解决: 使用 append([]int(nil), s...) 或 make + copy
坑 2:append 后切片脱离原数组
arr := [3]int{1, 2, 3}
s := arr[:]
s = append(s, 4) // 超cap,s 指向新数组
s[0] = 999
fmt.Println(arr) // [1 2 3] 未变!
fmt.Println(s) // [999 2 3 4]
坑 3:reslice 超出容量 panic
s := make([]int, 3, 5)
_ = s[:10] // panic: slice bounds out of range [:10] with capacity 5
10. 完整示例代码
package main
import "fmt"
func main() {
// 1. 创建
arr := [5]int{10, 20, 30, 40, 50}
s := arr[1:4]
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
// 2. 修改共享底层
s[0] = 999
fmt.Println("修改后 arr:", arr)
// 3. append
s = append(s, 60)
fmt.Printf("append后 s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
// 4. 超cap,分配新数组
s = append(s, 70, 80)
s[0] = 111
fmt.Println("新数组 s:", s)
fmt.Println("arr 是否被改:", arr)
// 5. copy
dup := make([]int, len(s))
copy(dup, s)
dup[0] = 0
fmt.Println("s:", s)
fmt.Println("dup:", dup)
}
输出:
s = [20 30 40], len = 3, cap = 4
修改后 arr: [10 999 30 40 50]
append后 s = [999 30 40 60], len = 4, cap = 4
新数组 s: [111 30 40 60 70 80]
arr 是否被改: [10 999 30 40 50]
s: [111 30 40 60 70 80]
dup: [0 30 40 60 70 80]
总结
| 要点 | 说明 |
|---|---|
| 切片是引用 | 共享底层数组,修改相互影响 |
| 左闭右开 | a[low:high],长度 = high - low |
| len vs cap | len 是元素数,cap 是可用容量 |
| append 可能扩容 | 超 cap 时会分配新数组,原数组不再受影响 |
| copy 深拷贝 | 需要独立数据时用 copy |