数组 - Go 数组和切片
数组和切片是 Go 中最基本的集合类型。数组是固定长度的,切片是动态的。理解它们的区别和用法是 Go 编程的基础。
数组
📝 数组定义和使用
package main
import "fmt"
func main() {
// 方式 1: 指定长度
var a [5]int
a[0] = 1
// 方式 2: 初始化
b := [5]int{1, 2, 3, 4, 5}
// 方式 3: 让编译器计算长度
c := [...] int{1, 2, 3, 4, 5, 6}
// 访问元素
fmt.Println(b[0]) // 1
fmt.Println(len(b)) // 5
// 遍历数组
for i, v := range b {
fmt.Printf("a[%d] = %d\n", i, v)
}
// 二维数组
var matrix [2][3]int
matrix[0][0] = 1
}
💡 数组要点
- 固定长度: 长度是类型的一部分
- 值类型: 赋值会复制整个数组
- 零值: 元素默认值为零值
- 长度固定: 不能改变长度
切片
📝 切片定义和操作
package main
import "fmt"
func main() {
// 方式 1: 从数组创建
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // [2 3 4]
// 方式 2: 直接创建切片
s1 := []int{1, 2, 3}
// 方式 3: make 创建
s2 := make([]int, 5) // len=5, cap=5
s3 := make([]int, 3, 5) // len=3, cap=5
// 切片操作
nums := []int{0, 1, 2, 3, 4, 5}
fmt.Println(nums[1:4]) // [1 2 3]
fmt.Println(nums[:3]) // [0 1 2]
fmt.Println(nums[3:]) // [3 4 5]
fmt.Println(nums[:]) // [0 1 2 3 4 5]
// len 和 cap
fmt.Printf("len: %d, cap: %d\n", len(s3), cap(s3))
}
📝 切片追加和复制
package main
import "fmt"
func main() {
// append: 追加元素
s := []int{1, 2, 3}
s = append(s, 4) // [1 2 3 4]
s = append(s, 5, 6) // [1 2 3 4 5 6]
// 追加另一个切片
s2 := []int{7, 8}
s = append(s, s2...) // [1 2 3 4 5 6 7 8]
// copy: 复制切片
src := []int{1, 2, 3, 4, 5}
dst := make([]int, 3)
n := copy(dst, src) // n=3, dst=[1 2 3]
fmt.Println(s)
fmt.Printf("Copied %d elements\n", n)
}
⚠️ 切片陷阱
// 陷阱 1: 共享底层数组
a := []int{1, 2, 3, 4, 5}
b := a[1:3] // [2 3]
b[0] = 99
fmt.Println(a) // [1 99 3 4 5] - a 被修改!
// 陷阱 2: append 可能扩容
s := make([]int, 0, 2)
s = append(s, 1, 2, 3) // 容量不足,创建新数组
// 避免:显式复制
b := make([]int, len(a[1:3]))
copy(b, a[1:3])
切片内部实现原理
📖 切片的底层数据结构
// runtime/slice.go - 切片的底层结构
type slice struct {
array unsafe.Pointer // 指向底层数组的指针
len int // 切片长度
cap int // 切片容量
}
// 切片三要素:
// 1. array: 指向底层数组的指针
// 2. len: 当前切片包含的元素个数
// 3. cap: 从切片起始位置到底层数组末尾的容量
切片 (slice)
→
array 指针
→
底层数组
len = 3
|
cap = 5
📝 切片结构图示
// 示例 1: 从数组创建切片
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // [2 3 4]
// 内存布局:
// arr: [1] [2] [3] [4] [5]
// ↑ ↑
// len=3 cap=4
// slice.array 指向 arr[1]
// 示例 2: 切片共享底层数组
a := []int{1, 2, 3, 4, 5}
b := a[1:3] // b.array 指向 a.array[1]
c := a[2:5] // c.array 指向 a.array[2]
// a, b, c 共享同一个底层数组!
// 修改 b 或 c 会影响 a
append 扩容机制
📖 append 的扩容策略
// 当容量不足时,append 会分配新数组
s := make([]int, 0, 2) // cap=2
s = append(s, 1) // len=1, cap=2
s = append(s, 2) // len=2, cap=2
s = append(s, 3) // len=3, cap=4 (扩容!)
// Go 1.18+ 扩容策略:
// 1. cap < 256: 容量翻倍 (oldCap * 2)
// 2. cap >= 256: 容量增长 25% (oldCap * 1.25)
// 3. 如果新容量超过预期,会调整到预期值
// 扩容流程:
// 1. 分配新的更大的底层数组
// 2. 复制旧数组元素到新数组
// 3. 添加新元素
// 4. 返回指向新数组的切片
💡 切片性能优化
- 预分配容量: 使用
make([]T, 0, cap)避免多次扩容 - 避免内存泄漏: 大数组的切片可能阻止 GC 回收
- 使用 copy: 需要独立数据时显式复制
- 注意容量:
append可能改变底层数组指针
最佳实践
✅ 切片使用建议
- 预分配容量: 知道大小时用 make([], 0, cap)
- 注意共享: 切片共享底层数组
- 使用 append: 不要手动管理容量
- 传递切片: 切片传递是引用语义