← 运算符 | Map →

数组 - 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: 不要手动管理容量
  • 传递切片: 切片传递是引用语义

📖 延伸阅读