1.4.1 数组与切片

1.4.1 数组与切片 #

数组和切片是 Go 语言中用于存储序列数据的两种重要数据类型。虽然它们在功能上相似,但在内存管理、性能特点和使用方式上存在显著差异。理解这两种数据类型的特点和适用场景,是掌握 Go 语言编程的重要基础。

数组(Array) #

数组的基本概念 #

数组是具有固定长度的同类型元素序列。在 Go 语言中,数组的长度是类型的一部分,这意味着 [3]int[4]int 是完全不同的类型。数组在编译时就确定了大小,存储在栈上,具有很好的性能特点。

数组的声明和初始化 #

package main

import "fmt"

func main() {
    // 1. 声明数组的几种方式

    // 方式一:声明后赋值
    var numbers [5]int
    numbers[0] = 10
    numbers[1] = 20
    numbers[2] = 30
    fmt.Printf("数组 numbers: %v\n", numbers)

    // 方式二:声明时初始化
    var fruits = [3]string{"苹果", "香蕉", "橙子"}
    fmt.Printf("水果数组: %v\n", fruits)

    // 方式三:使用短变量声明
    colors := [4]string{"红色", "绿色", "蓝色", "黄色"}
    fmt.Printf("颜色数组: %v\n", colors)

    // 方式四:让编译器推断长度
    scores := [...]int{85, 92, 78, 96, 88}
    fmt.Printf("成绩数组: %v, 长度: %d\n", scores, len(scores))

    // 方式五:指定索引初始化
    weekdays := [7]string{
        0: "星期日",
        1: "星期一",
        2: "星期二",
        6: "星期六", // 其他位置为零值
    }
    fmt.Printf("星期数组: %v\n", weekdays)
}

数组的基本操作 #

package main

import "fmt"

func main() {
    // 创建一个整数数组
    numbers := [5]int{10, 20, 30, 40, 50}

    // 1. 访问数组元素
    fmt.Printf("第一个元素: %d\n", numbers[0])
    fmt.Printf("最后一个元素: %d\n", numbers[len(numbers)-1])

    // 2. 修改数组元素
    numbers[2] = 35
    fmt.Printf("修改后的数组: %v\n", numbers)

    // 3. 遍历数组
    fmt.Println("使用传统 for 循环遍历:")
    for i := 0; i < len(numbers); i++ {
        fmt.Printf("索引 %d: 值 %d\n", i, numbers[i])
    }

    fmt.Println("使用 range 遍历:")
    for index, value := range numbers {
        fmt.Printf("索引 %d: 值 %d\n", index, value)
    }

    // 4. 只要值,不要索引
    fmt.Println("只输出值:")
    for _, value := range numbers {
        fmt.Printf("%d ", value)
    }
    fmt.Println()

    // 5. 数组比较
    arr1 := [3]int{1, 2, 3}
    arr2 := [3]int{1, 2, 3}
    arr3 := [3]int{1, 2, 4}

    fmt.Printf("arr1 == arr2: %t\n", arr1 == arr2) // true
    fmt.Printf("arr1 == arr3: %t\n", arr1 == arr3) // false
}

多维数组 #

package main

import "fmt"

func main() {
    // 1. 二维数组声明和初始化
    var matrix [3][4]int

    // 填充二维数组
    for i := 0; i < 3; i++ {
        for j := 0; j < 4; j++ {
            matrix[i][j] = i*4 + j + 1
        }
    }

    fmt.Println("二维数组:")
    for i := 0; i < 3; i++ {
        for j := 0; j < 4; j++ {
            fmt.Printf("%2d ", matrix[i][j])
        }
        fmt.Println()
    }

    // 2. 初始化时赋值
    grid := [3][3]int{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
    }

    fmt.Println("网格数组:")
    for _, row := range grid {
        for _, value := range row {
            fmt.Printf("%d ", value)
        }
        fmt.Println()
    }

    // 3. 三维数组示例
    cube := [2][2][2]int{
        {
            {1, 2},
            {3, 4},
        },
        {
            {5, 6},
            {7, 8},
        },
    }

    fmt.Println("三维数组:")
    for i, plane := range cube {
        fmt.Printf("平面 %d:\n", i)
        for j, row := range plane {
            fmt.Printf("  行 %d: ", j)
            for _, value := range row {
                fmt.Printf("%d ", value)
            }
            fmt.Println()
        }
    }
}

切片(Slice) #

切片的基本概念 #

切片是对数组的抽象,提供了动态数组的功能。切片本身不存储数据,而是引用底层数组的一个连续片段。切片由三个部分组成:指向底层数组的指针、长度(length)和容量(capacity)。

切片的创建方式 #

package main

import "fmt"

func main() {
    // 1. 从数组创建切片
    arr := [6]int{10, 20, 30, 40, 50, 60}
    slice1 := arr[1:4] // 包含索引1到3的元素
    fmt.Printf("从数组创建切片: %v\n", slice1)

    // 2. 使用 make 函数创建切片
    slice2 := make([]int, 5)      // 长度为5,容量为5
    slice3 := make([]int, 3, 10)  // 长度为3,容量为10
    fmt.Printf("make创建的切片2: %v, 长度: %d, 容量: %d\n",
               slice2, len(slice2), cap(slice2))
    fmt.Printf("make创建的切片3: %v, 长度: %d, 容量: %d\n",
               slice3, len(slice3), cap(slice3))

    // 3. 使用切片字面量
    slice4 := []string{"Go", "Python", "Java", "JavaScript"}
    fmt.Printf("字面量创建的切片: %v\n", slice4)

    // 4. 从切片创建切片
    slice5 := slice4[1:3]
    fmt.Printf("从切片创建切片: %v\n", slice5)

    // 5. 切片的切片操作详解
    numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Printf("原始切片: %v\n", numbers)

    // 各种切片操作
    fmt.Printf("numbers[2:5]: %v\n", numbers[2:5])   // [2 3 4]
    fmt.Printf("numbers[:4]: %v\n", numbers[:4])     // [0 1 2 3]
    fmt.Printf("numbers[6:]: %v\n", numbers[6:])     // [6 7 8 9]
    fmt.Printf("numbers[:]: %v\n", numbers[:])       // 完整切片
}

切片的动态操作 #

package main

import "fmt"

func main() {
    // 1. append 操作
    var slice []int
    fmt.Printf("初始切片: %v, 长度: %d, 容量: %d\n",
               slice, len(slice), cap(slice))

    // 添加单个元素
    slice = append(slice, 1)
    slice = append(slice, 2, 3, 4)
    fmt.Printf("添加元素后: %v, 长度: %d, 容量: %d\n",
               slice, len(slice), cap(slice))

    // 添加另一个切片
    other := []int{5, 6, 7}
    slice = append(slice, other...)
    fmt.Printf("添加切片后: %v, 长度: %d, 容量: %d\n",
               slice, len(slice), cap(slice))

    // 2. copy 操作
    source := []int{1, 2, 3, 4, 5}
    dest := make([]int, len(source))

    n := copy(dest, source)
    fmt.Printf("复制了 %d 个元素\n", n)
    fmt.Printf("源切片: %v\n", source)
    fmt.Printf("目标切片: %v\n", dest)

    // 部分复制
    partial := make([]int, 3)
    copy(partial, source[1:4])
    fmt.Printf("部分复制: %v\n", partial)

    // 3. 删除操作(通过切片重组实现)
    numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Printf("原始切片: %v\n", numbers)

    // 删除索引为3的元素(值为4)
    index := 3
    numbers = append(numbers[:index], numbers[index+1:]...)
    fmt.Printf("删除索引3后: %v\n", numbers)

    // 删除前两个元素
    numbers = numbers[2:]
    fmt.Printf("删除前两个元素: %v\n", numbers)

    // 删除最后一个元素
    numbers = numbers[:len(numbers)-1]
    fmt.Printf("删除最后一个元素: %v\n", numbers)
}

切片的内存管理 #

package main

import "fmt"

func main() {
    // 1. 切片扩容机制演示
    slice := make([]int, 0, 1)
    fmt.Printf("初始: 长度=%d, 容量=%d\n", len(slice), cap(slice))

    for i := 0; i < 10; i++ {
        slice = append(slice, i)
        fmt.Printf("添加 %d: 长度=%d, 容量=%d\n",
                   i, len(slice), cap(slice))
    }

    // 2. 切片共享底层数组的问题
    original := []int{1, 2, 3, 4, 5}
    slice1 := original[1:3]  // [2, 3]
    slice2 := original[2:4]  // [3, 4]

    fmt.Printf("原始切片: %v\n", original)
    fmt.Printf("切片1: %v\n", slice1)
    fmt.Printf("切片2: %v\n", slice2)

    // 修改 slice1 会影响原始切片和 slice2
    slice1[1] = 99
    fmt.Printf("修改 slice1[1] 后:\n")
    fmt.Printf("原始切片: %v\n", original)
    fmt.Printf("切片1: %v\n", slice1)
    fmt.Printf("切片2: %v\n", slice2)

    // 3. 避免共享问题的方法
    safe := make([]int, len(slice1))
    copy(safe, slice1)
    safe[0] = 888

    fmt.Printf("安全复制后修改:\n")
    fmt.Printf("原始切片: %v\n", original)
    fmt.Printf("安全切片: %v\n", safe)
}

数组与切片的比较 #

性能和使用场景 #

package main

import (
    "fmt"
    "time"
)

// 数组作为参数(值传递)
func processArray(arr [1000]int) int {
    sum := 0
    for _, v := range arr {
        sum += v
    }
    return sum
}

// 切片作为参数(引用传递)
func processSlice(slice []int) int {
    sum := 0
    for _, v := range slice {
        sum += v
    }
    return sum
}

func main() {
    // 1. 内存使用比较
    arr := [1000]int{}
    slice := make([]int, 1000)

    // 填充数据
    for i := 0; i < 1000; i++ {
        arr[i] = i
        slice[i] = i
    }

    // 2. 性能测试
    start := time.Now()
    for i := 0; i < 10000; i++ {
        processArray(arr)
    }
    arrayTime := time.Since(start)

    start = time.Now()
    for i := 0; i < 10000; i++ {
        processSlice(slice)
    }
    sliceTime := time.Since(start)

    fmt.Printf("数组处理时间: %v\n", arrayTime)
    fmt.Printf("切片处理时间: %v\n", sliceTime)

    // 3. 使用场景建议
    fmt.Println("\n使用场景建议:")
    fmt.Println("数组适用于:")
    fmt.Println("- 固定大小的数据集合")
    fmt.Println("- 需要高性能的场景")
    fmt.Println("- 作为其他数据结构的基础")

    fmt.Println("\n切片适用于:")
    fmt.Println("- 动态大小的数据集合")
    fmt.Println("- 需要灵活操作的场景")
    fmt.Println("- 大部分日常编程需求")
}

实际应用示例 #

数据处理和算法实现 #

package main

import (
    "fmt"
    "sort"
)

// 学生成绩管理系统
type Student struct {
    Name  string
    Score int
}

func main() {
    // 1. 成绩统计
    scores := []int{85, 92, 78, 96, 88, 76, 94, 82, 90, 87}

    fmt.Printf("原始成绩: %v\n", scores)

    // 计算平均分
    sum := 0
    for _, score := range scores {
        sum += score
    }
    average := float64(sum) / float64(len(scores))
    fmt.Printf("平均分: %.2f\n", average)

    // 找出最高分和最低分
    maxScore := scores[0]
    minScore := scores[0]
    for _, score := range scores {
        if score > maxScore {
            maxScore = score
        }
        if score < minScore {
            minScore = score
        }
    }
    fmt.Printf("最高分: %d, 最低分: %d\n", maxScore, minScore)

    // 2. 成绩分级
    gradeCount := make(map[string]int)
    for _, score := range scores {
        switch {
        case score >= 90:
            gradeCount["优秀"]++
        case score >= 80:
            gradeCount["良好"]++
        case score >= 70:
            gradeCount["中等"]++
        case score >= 60:
            gradeCount["及格"]++
        default:
            gradeCount["不及格"]++
        }
    }

    fmt.Println("成绩分布:")
    for grade, count := range gradeCount {
        fmt.Printf("%s: %d人\n", grade, count)
    }

    // 3. 学生排序
    students := []Student{
        {"张三", 85},
        {"李四", 92},
        {"王五", 78},
        {"赵六", 96},
        {"钱七", 88},
    }

    fmt.Println("\n学生成绩排序:")
    fmt.Println("排序前:")
    for _, student := range students {
        fmt.Printf("%s: %d分\n", student.Name, student.Score)
    }

    // 按成绩降序排序
    sort.Slice(students, func(i, j int) bool {
        return students[i].Score > students[j].Score
    })

    fmt.Println("排序后:")
    for i, student := range students {
        fmt.Printf("第%d名 %s: %d分\n", i+1, student.Name, student.Score)
    }
}

矩阵运算 #

package main

import "fmt"

// 矩阵类型定义
type Matrix [][]int

// 创建矩阵
func NewMatrix(rows, cols int) Matrix {
    matrix := make(Matrix, rows)
    for i := range matrix {
        matrix[i] = make([]int, cols)
    }
    return matrix
}

// 矩阵加法
func (m Matrix) Add(other Matrix) Matrix {
    if len(m) != len(other) || len(m[0]) != len(other[0]) {
        panic("矩阵维度不匹配")
    }

    result := NewMatrix(len(m), len(m[0]))
    for i := 0; i < len(m); i++ {
        for j := 0; j < len(m[0]); j++ {
            result[i][j] = m[i][j] + other[i][j]
        }
    }
    return result
}

// 矩阵乘法
func (m Matrix) Multiply(other Matrix) Matrix {
    if len(m[0]) != len(other) {
        panic("矩阵维度不匹配")
    }

    result := NewMatrix(len(m), len(other[0]))
    for i := 0; i < len(m); i++ {
        for j := 0; j < len(other[0]); j++ {
            for k := 0; k < len(other); k++ {
                result[i][j] += m[i][k] * other[k][j]
            }
        }
    }
    return result
}

// 打印矩阵
func (m Matrix) Print() {
    for _, row := range m {
        for _, val := range row {
            fmt.Printf("%3d ", val)
        }
        fmt.Println()
    }
}

func main() {
    // 创建矩阵
    matrix1 := Matrix{
        {1, 2, 3},
        {4, 5, 6},
    }

    matrix2 := Matrix{
        {7, 8, 9},
        {10, 11, 12},
    }

    matrix3 := Matrix{
        {1, 2},
        {3, 4},
        {5, 6},
    }

    fmt.Println("矩阵1:")
    matrix1.Print()

    fmt.Println("矩阵2:")
    matrix2.Print()

    fmt.Println("矩阵3:")
    matrix3.Print()

    // 矩阵加法
    fmt.Println("矩阵1 + 矩阵2:")
    sum := matrix1.Add(matrix2)
    sum.Print()

    // 矩阵乘法
    fmt.Println("矩阵1 × 矩阵3:")
    product := matrix1.Multiply(matrix3)
    product.Print()
}

动态数组实现 #

package main

import "fmt"

// 动态数组结构
type DynamicArray struct {
    data     []int
    size     int
    capacity int
}

// 创建动态数组
func NewDynamicArray(capacity int) *DynamicArray {
    return &DynamicArray{
        data:     make([]int, capacity),
        size:     0,
        capacity: capacity,
    }
}

// 添加元素
func (da *DynamicArray) Add(value int) {
    if da.size >= da.capacity {
        da.resize()
    }
    da.data[da.size] = value
    da.size++
}

// 扩容
func (da *DynamicArray) resize() {
    newCapacity := da.capacity * 2
    newData := make([]int, newCapacity)
    copy(newData, da.data)
    da.data = newData
    da.capacity = newCapacity
    fmt.Printf("数组扩容到: %d\n", newCapacity)
}

// 获取元素
func (da *DynamicArray) Get(index int) int {
    if index < 0 || index >= da.size {
        panic("索引越界")
    }
    return da.data[index]
}

// 删除元素
func (da *DynamicArray) Remove(index int) {
    if index < 0 || index >= da.size {
        panic("索引越界")
    }

    for i := index; i < da.size-1; i++ {
        da.data[i] = da.data[i+1]
    }
    da.size--
}

// 获取大小
func (da *DynamicArray) Size() int {
    return da.size
}

// 打印数组
func (da *DynamicArray) Print() {
    fmt.Printf("数组内容: [")
    for i := 0; i < da.size; i++ {
        fmt.Printf("%d", da.data[i])
        if i < da.size-1 {
            fmt.Printf(", ")
        }
    }
    fmt.Printf("], 大小: %d, 容量: %d\n", da.size, da.capacity)
}

func main() {
    // 创建动态数组
    arr := NewDynamicArray(2)

    // 添加元素
    for i := 1; i <= 10; i++ {
        arr.Add(i * 10)
        arr.Print()
    }

    // 访问元素
    fmt.Printf("索引2的元素: %d\n", arr.Get(2))

    // 删除元素
    fmt.Println("删除索引1的元素:")
    arr.Remove(1)
    arr.Print()

    // 继续添加元素
    arr.Add(999)
    arr.Print()
}

最佳实践和注意事项 #

性能优化建议 #

package main

import (
    "fmt"
    "time"
)

func main() {
    // 1. 预分配切片容量
    fmt.Println("切片容量预分配的重要性:")

    // 不好的做法:频繁扩容
    start := time.Now()
    var badSlice []int
    for i := 0; i < 100000; i++ {
        badSlice = append(badSlice, i)
    }
    badTime := time.Since(start)

    // 好的做法:预分配容量
    start = time.Now()
    goodSlice := make([]int, 0, 100000)
    for i := 0; i < 100000; i++ {
        goodSlice = append(goodSlice, i)
    }
    goodTime := time.Since(start)

    fmt.Printf("未预分配时间: %v\n", badTime)
    fmt.Printf("预分配时间: %v\n", goodTime)
    fmt.Printf("性能提升: %.2fx\n", float64(badTime)/float64(goodTime))

    // 2. 避免切片内存泄漏
    fmt.Println("\n避免切片内存泄漏:")

    // 问题:保留大切片的小部分
    bigSlice := make([]int, 1000000)
    for i := range bigSlice {
        bigSlice[i] = i
    }

    // 不好的做法:直接切片,会保持对整个底层数组的引用
    badSubSlice := bigSlice[0:10]

    // 好的做法:复制需要的部分
    goodSubSlice := make([]int, 10)
    copy(goodSubSlice, bigSlice[0:10])

    fmt.Printf("不好的子切片长度: %d, 容量: %d\n",
               len(badSubSlice), cap(badSubSlice))
    fmt.Printf("好的子切片长度: %d, 容量: %d\n",
               len(goodSubSlice), cap(goodSubSlice))

    // 3. 正确的切片删除方法
    fmt.Println("\n切片删除的最佳实践:")

    numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    fmt.Printf("原始切片: %v\n", numbers)

    // 删除中间元素的高效方法
    index := 4 // 删除值为5的元素
    numbers = append(numbers[:index], numbers[index+1:]...)
    fmt.Printf("删除索引4后: %v\n", numbers)

    // 如果不需要保持顺序,可以用更高效的方法
    numbers2 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    index = 4
    numbers2[index] = numbers2[len(numbers2)-1] // 用最后一个元素覆盖
    numbers2 = numbers2[:len(numbers2)-1]       // 缩短切片
    fmt.Printf("快速删除后: %v\n", numbers2)
}

小结 #

本节详细介绍了 Go 语言中的数组和切片,主要内容包括:

数组特点 #

  • 固定长度:编译时确定大小,长度是类型的一部分
  • 值类型:赋值和传参时会复制整个数组
  • 高性能:直接存储在栈上,访问速度快
  • 适用场景:固定大小的数据集合,对性能要求高的场景

切片特点 #

  • 动态长度:可以动态增长和缩减
  • 引用类型:指向底层数组,传参时只复制切片头
  • 灵活操作:支持 append、copy 等丰富的操作
  • 适用场景:大部分日常编程需求,需要动态调整大小的数据集合

最佳实践 #

  • 优先使用切片而不是数组
  • 预分配切片容量以提高性能
  • 注意切片的内存泄漏问题
  • 理解切片的底层机制,避免意外的数据共享

性能考虑 #

  • 数组传参会复制整个数组,切片传参只复制切片头
  • 切片扩容时会重新分配内存,预分配可以避免频繁扩容
  • 大切片的小部分引用可能导致内存泄漏

掌握数组和切片的使用是 Go 语言编程的基础,它们是构建更复杂数据结构和算法的重要工具。


练习题:

  1. 实现一个函数,找出数组中的第二大元素
  2. 编写程序实现矩阵的转置操作
  3. 实现一个动态栈,支持 push、pop 和 peek 操作
  4. 编写函数合并两个有序切片为一个有序切片
  5. 实现一个环形缓冲区,支持读写操作