数组
数组是 Go 语言中最基本的数据结构,用于存储固定长度的同类型元素序列。理解数组对于掌握 Go 的内存管理和性能优化很重要。
📋 学习目标
- 掌握数组的声明和初始化
- 理解数组的内存布局
- 学会使用多维数组
- 掌握数组的遍历和操作
- 了解数组与切片的区别
- 学会使用数组作为函数参数
🏗️ 数组基础
数组声明和初始化
go
package main
import "fmt"
func main() {
// 声明数组(零值)
var arr1 [5]int
fmt.Printf("零值数组: %v\n", arr1)
// 声明并初始化
var arr2 = [5]int{1, 2, 3, 4, 5}
fmt.Printf("完整初始化: %v\n", arr2)
// 自动推断长度
arr3 := [...]int{10, 20, 30, 40, 50}
fmt.Printf("自动长度: %v (长度: %d)\n", arr3, len(arr3))
// 指定索引初始化
arr4 := [5]int{0: 100, 2: 200, 4: 300}
fmt.Printf("指定索引: %v\n", arr4)
// 部分初始化(其余为零值)
arr5 := [5]int{1, 2}
fmt.Printf("部分初始化: %v\n", arr5)
// 不同类型的数组
var stringArr [3]string
var boolArr [4]bool
var floatArr [3]float64
fmt.Printf("字符串数组: %v\n", stringArr)
fmt.Printf("布尔数组: %v\n", boolArr)
fmt.Printf("浮点数组: %v\n", floatArr)
}数组的长度和容量
go
package main
import "fmt"
func main() {
// 数组的长度是固定的
arr := [5]int{1, 2, 3, 4, 5}
fmt.Printf("数组: %v\n", arr)
fmt.Printf("长度: %d\n", len(arr))
fmt.Printf("容量: %d\n", cap(arr)) // 数组容量等于长度
// 尝试修改长度(编译错误)
// arr = append(arr, 6) // 编译错误:数组是固定长度的
// 不同长度的数组是不同类型
var shortArr [3]int
var longArr [5]int
// shortArr = longArr // 编译错误:类型不匹配
_ = shortArr // 避免未使用变量错误
fmt.Println("数组长度在编译时就确定了")
}🎯 数组操作
数组访问和修改
go
package main
import "fmt"
func main() {
// 基本访问
numbers := [5]int{10, 20, 30, 40, 50}
fmt.Printf("numbers[0] = %d\n", numbers[0]) // 10
fmt.Printf("numbers[4] = %d\n", numbers[4]) // 50
// 修改元素
numbers[2] = 300
fmt.Printf("修改后: %v\n", numbers)
// 边界检查
if len(numbers) > 3 {
fmt.Printf("numbers[3] = %d\n", numbers[3])
}
// 超出边界访问(运行时 panic)
// fmt.Printf("numbers[5] = %d\n", numbers[5]) // panic: index out of range
// 使用常量作为索引
const lastIndex = 4
fmt.Printf("最后元素: %d\n", numbers[lastIndex])
// 动态索引访问
for i := 0; i < len(numbers); i++ {
fmt.Printf("索引 %d: %d\n", i, numbers[i])
}
}数组遍历
go
package main
import "fmt"
func main() {
// 传统 for 循环遍历
arr := [5]string{"苹果", "香蕉", "橙子", "葡萄", "西瓜"}
fmt.Println("传统 for 循环:")
for i := 0; i < len(arr); i++ {
fmt.Printf("arr[%d] = %s\n", i, arr[i])
}
// for-range 遍历
fmt.Println("\nfor-range 遍历:")
for index, value := range arr {
fmt.Printf("索引 %d: %s\n", index, value)
}
// 只要值,忽略索引
fmt.Println("\n只获取值:")
for _, value := range arr {
fmt.Printf("水果: %s\n", value)
}
// 只要索引,忽略值(不常用)
fmt.Println("\n只获取索引:")
for index := range arr {
fmt.Printf("索引: %d\n", index)
}
}📐 多维数组
二维数组
go
package main
import "fmt"
func main() {
// 二维数组声明和初始化
var matrix [3][4]int // 3行4列的零值矩阵
// 完整初始化
matrix2 := [3][4]int{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
}
fmt.Printf("二维数组: %v\n", matrix2)
// 访问和修改
matrix2[1][2] = 100
fmt.Printf("修改后: %v\n", matrix2)
fmt.Printf("matrix[1][2] = %d\n", matrix2[1][2])
// 遍历二维数组
fmt.Println("\n遍历二维数组:")
for i := 0; i < len(matrix2); i++ {
for j := 0; j < len(matrix2[i]); j++ {
fmt.Printf("matrix[%d][%d] = %d\n", i, j, matrix2[i][j])
}
}
// for-range 遍历二维数组
fmt.Println("\nfor-range 遍历:")
for i, row := range matrix2 {
for j, value := range row {
fmt.Printf("[%d][%d] = %d\n", i, j, value)
}
}
}三维数组
go
package main
import "fmt"
func main() {
// 三维数组(3x2x4)
cube := [3][2][4]int{
{
{1, 2, 3, 4},
{5, 6, 7, 8},
},
{
{9, 10, 11, 12},
{13, 14, 15, 16},
},
{
{17, 18, 19, 20},
{21, 22, 23, 24},
},
}
fmt.Printf("三维数组大小: %d x %d x %d\n",
len(cube), len(cube[0]), len(cube[0][0]))
// 访问三维数组元素
fmt.Printf("cube[1][0][2] = %d\n", cube[1][0][2])
// 修改元素
cube[2][1][3] = 999
fmt.Printf("修改后 cube[2][1][3] = %d\n", cube[2][1][3])
// 遍历三维数组
fmt.Println("\n遍历三维数组:")
for x := 0; x < len(cube); x++ {
for y := 0; y < len(cube[x]); y++ {
for z := 0; z < len(cube[x][y]); z++ {
fmt.Printf("cube[%d][%d][%d] = %d\n",
x, y, z, cube[x][y][z])
}
}
}
}🔄 数组与函数
数组作为函数参数
go
package main
import "fmt"
// 数组作为参数(需要指定长度)
func processArray(arr [5]int) {
fmt.Printf("处理数组: %v\n", arr)
for i, value := range arr {
arr[i] = value * 2 // 修改的是副本
}
fmt.Printf("处理后: %v\n", arr)
}
// 使用指针修改原数组
func modifyArray(arr *[5]int) {
fmt.Printf("修改原数组: %v\n", *arr)
for i := 0; i < len(arr); i++ {
(*arr)[i] = (*arr)[i] * 2
}
fmt.Printf("修改后: %v\n", *arr)
}
// 返回数组
func createArray() [5]int {
return [5]int{1, 1, 2, 3, 5} // 斐波那契数列前5项
}
func main() {
// 传递数组(值传递)
original := [5]int{1, 2, 3, 4, 5}
fmt.Printf("原数组: %v\n", original)
processArray(original)
fmt.Printf("原数组未被改变: %v\n", original)
// 传递数组指针
modifyArray(&original)
fmt.Printf("原数组被改变: %v\n", original)
// 接收返回的数组
newArray := createArray()
fmt.Printf("新数组: %v\n", newArray)
}数组切片操作
go
package main
import "fmt"
// 从数组创建切片
func arrayToSlice(arr [5]int) []int {
// 创建整个数组的切片
slice1 := arr[:]
fmt.Printf("整个数组切片: %v (长度: %d, 容量: %d)\n",
slice1, len(slice1), cap(slice1))
// 创建部分切片
slice2 := arr[1:4]
fmt.Printf("部分切片 [1:4]: %v (长度: %d, 容量: %d)\n",
slice2, len(slice2), cap(slice2))
// 创建从开始的切片
slice3 := arr[:3]
fmt.Printf("开头切片 [:3]: %v\n", slice3)
// 创建到末尾的切片
slice4 := arr[2:]
fmt.Printf("末尾切片 [2:]: %v\n", slice4)
return slice1
}
func main() {
numbers := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Printf("原数组: %v\n", numbers)
slice := arrayToSlice(numbers)
// 修改切片会影响原数组
slice[0] = 100
fmt.Printf("修改切片后原数组: %v\n", numbers)
fmt.Printf("修改后的切片: %v\n", slice)
}🎯 实际应用
数组作为缓存和缓冲区
go
package main
import "fmt"
// 使用数组作为循环缓冲区
type CircularBuffer struct {
buffer [5]int
head int
tail int
count int
}
func NewCircularBuffer() *CircularBuffer {
return &CircularBuffer{}
}
func (cb *CircularBuffer) Push(value int) bool {
if cb.count >= len(cb.buffer) {
return false // 缓冲区满
}
cb.buffer[cb.tail] = value
cb.tail = (cb.tail + 1) % len(cb.buffer)
cb.count++
return true
}
func (cb *CircularBuffer) Pop() (int, bool) {
if cb.count == 0 {
return 0, false // 缓冲区空
}
value := cb.buffer[cb.head]
cb.head = (cb.head + 1) % len(cb.buffer)
cb.count--
return value, true
}
func (cb *CircularBuffer) IsEmpty() bool {
return cb.count == 0
}
func (cb *CircularBuffer) IsFull() bool {
return cb.count >= len(cb.buffer)
}
func main() {
buffer := NewCircularBuffer()
// 测试缓冲区
fmt.Println("添加元素:")
for i := 1; i <= 7; i++ {
if ok := buffer.Push(i); ok {
fmt.Printf("添加: %d\n", i)
} else {
fmt.Printf("缓冲区满,无法添加: %d\n", i)
}
}
fmt.Println("\n取出元素:")
for !buffer.IsEmpty() {
if value, ok := buffer.Pop(); ok {
fmt.Printf("取出: %d\n", value)
}
}
}数组排序
go
package main
import "fmt"
// 冒泡排序(原地排序)
func bubbleSort(arr *[5]int) {
n := len(arr)
for i := 0; i < n-1; i++ {
for j := 0; j < n-i-1; j++ {
if arr[j] > arr[j+1] {
// 交换元素
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
}
func main() {
// 测试排序
numbers := [5]int{64, 34, 25, 12, 22}
fmt.Printf("排序前: %v\n", numbers)
bubbleSort(&numbers)
fmt.Printf("排序后: %v\n", numbers)
// 验证排序结果
isSorted := true
for i := 0; i < len(numbers)-1; i++ {
if numbers[i] > numbers[i+1] {
isSorted = false
break
}
}
fmt.Printf("排序正确: %t\n", isSorted)
}🧪 实践练习
练习 1:矩阵运算
go
// 要求:
// 1. 实现矩阵加法
// 2. 实现矩阵转置
// 3. 实现矩阵乘法
// 4. 添加矩阵打印功能参考实现:
go
package main
import "fmt"
// 矩阵加法
func matrixAdd(a, b [2][3]int) ([2][3]int, error) {
// 检查维度
if len(a) != len(b) || len(a[0]) != len(b[0]) {
return [2][3]int{}, fmt.Errorf("矩阵维度不匹配")
}
var result [2][3]int
for i := 0; i < len(a); i++ {
for j := 0; j < len(a[0]); j++ {
result[i][j] = a[i][j] + b[i][j]
}
}
return result, nil
}
// 矩阵打印
func printMatrix(name string, matrix [2][3]int) {
fmt.Printf("%s:\n", name)
for i, row := range matrix {
for _, value := range row {
fmt.Printf("%4d", value)
}
fmt.Println()
}
}
func main() {
matrix1 := [2][3]int{{1, 2, 3}, {4, 5, 6}}
matrix2 := [2][3]int{{7, 8, 9}, {10, 11, 12}}
printMatrix("矩阵1", matrix1)
printMatrix("矩阵2", matrix2)
result, err := matrixAdd(matrix1, matrix2)
if err != nil {
fmt.Printf("错误: %v\n", err)
return
}
printMatrix("结果", result)
}练习 2:统计计算
go
// 要求:
// 1. 实现数字统计(最大值、最小值、平均值)
// 2. 实现标准差计算
// 3. 实现中位数计算
// 4. 使用数组存储计算结果练习 3:数组合并和查找
go
// 要求:
// 1. 实现两个有序数组的合并
// 2. 实现二分查找
// 3. 实现数组去重
// 4. 分析算法复杂度🤔 思考题
数组和切片的主要区别是什么?
- 提示:考虑长度、内存分配、灵活性
什么时候应该使用数组而不是切片?
- 提示:考虑性能需求、固定大小场景
多维数组在内存中是如何存储的?
- 提示:考虑行主序和列主序
为什么数组是值类型?
- 提示:考虑 Go 的设计哲学和内存安全
📚 扩展阅读
⏭️ 下一章节
切片 → 学习 Go 语言的切片类型
💡 小贴士: 数组在 Go 中是值类型,这意味着赋值和传参都是复制整个数组。记住:当需要修改原数据或处理大量数据时,优先考虑使用切片!
