Files
golang/golang-learning/04-data-structures/01-arrays.go
2025-08-24 01:15:18 +08:00

554 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
01-arrays.go - Go 语言数组详解
学习目标:
1. 理解数组的概念和特性
2. 掌握数组的声明和初始化
3. 学会数组的基本操作
4. 了解数组的内存布局
5. 掌握数组的实际应用场景
知识点:
- 数组的定义和特性
- 数组的声明和初始化方式
- 数组的访问和修改
- 数组的长度和容量
- 多维数组
- 数组的比较和拷贝
- 数组 vs 切片的区别
*/
package main
import "fmt"
func main() {
fmt.Println("=== Go 语言数组详解 ===\n")
// 演示数组的基本概念
demonstrateBasicArrays()
// 演示数组的声明和初始化
demonstrateArrayDeclaration()
// 演示数组的操作
demonstrateArrayOperations()
// 演示多维数组
demonstrateMultiDimensionalArrays()
// 演示数组的特性
demonstrateArrayProperties()
// 演示数组的实际应用
demonstratePracticalApplications()
// 演示数组 vs 切片
demonstrateArrayVsSlice()
}
// demonstrateBasicArrays 演示数组的基本概念
func demonstrateBasicArrays() {
fmt.Println("1. 数组的基本概念:")
// 数组的基本特性
fmt.Printf(" 数组的基本特性:\n")
fmt.Printf(" - 固定长度的元素序列\n")
fmt.Printf(" - 元素类型相同\n")
fmt.Printf(" - 长度是类型的一部分\n")
fmt.Printf(" - 零值是所有元素都为零值的数组\n")
fmt.Printf(" - 值类型,赋值和传参会拷贝整个数组\n")
// 基本数组示例
fmt.Printf(" 基本数组示例:\n")
var numbers [5]int // 声明长度为5的整数数组
fmt.Printf(" 声明数组: %v\n", numbers)
fmt.Printf(" 数组长度: %d\n", len(numbers))
fmt.Printf(" 数组类型: %T\n", numbers)
// 数组元素访问
numbers[0] = 10
numbers[1] = 20
numbers[2] = 30
fmt.Printf(" 修改元素后: %v\n", numbers)
fmt.Printf(" 第一个元素: %d\n", numbers[0])
fmt.Printf(" 最后一个元素: %d\n", numbers[len(numbers)-1])
fmt.Println()
}
// demonstrateArrayDeclaration 演示数组的声明和初始化
func demonstrateArrayDeclaration() {
fmt.Println("2. 数组的声明和初始化:")
// 方式1: 声明后赋值
fmt.Printf(" 方式1 - 声明后赋值:\n")
var arr1 [3]int
arr1[0] = 1
arr1[1] = 2
arr1[2] = 3
fmt.Printf(" arr1: %v\n", arr1)
// 方式2: 声明时初始化
fmt.Printf(" 方式2 - 声明时初始化:\n")
var arr2 = [3]int{10, 20, 30}
fmt.Printf(" arr2: %v\n", arr2)
// 方式3: 短变量声明
fmt.Printf(" 方式3 - 短变量声明:\n")
arr3 := [4]string{"Go", "Python", "Java", "C++"}
fmt.Printf(" arr3: %v\n", arr3)
// 方式4: 自动推断长度
fmt.Printf(" 方式4 - 自动推断长度:\n")
arr4 := [...]int{1, 2, 3, 4, 5} // ... 让编译器计算长度
fmt.Printf(" arr4: %v (长度: %d)\n", arr4, len(arr4))
// 方式5: 指定索引初始化
fmt.Printf(" 方式5 - 指定索引初始化:\n")
arr5 := [5]int{1: 10, 3: 30, 4: 40} // 指定索引1,3,4的值
fmt.Printf(" arr5: %v\n", arr5)
// 方式6: 混合初始化
fmt.Printf(" 方式6 - 混合初始化:\n")
arr6 := [...]string{0: "first", 2: "third", "fourth"} // 混合指定索引和顺序
fmt.Printf(" arr6: %v (长度: %d)\n", arr6, len(arr6))
// 不同类型的数组
fmt.Printf(" 不同类型的数组:\n")
boolArray := [3]bool{true, false, true}
floatArray := [4]float64{1.1, 2.2, 3.3, 4.4}
stringArray := [2]string{"hello", "world"}
fmt.Printf(" 布尔数组: %v\n", boolArray)
fmt.Printf(" 浮点数组: %v\n", floatArray)
fmt.Printf(" 字符串数组: %v\n", stringArray)
fmt.Println()
}
// demonstrateArrayOperations 演示数组的操作
func demonstrateArrayOperations() {
fmt.Println("3. 数组的操作:")
// 数组遍历
fmt.Printf(" 数组遍历:\n")
fruits := [4]string{"apple", "banana", "cherry", "date"}
// 使用索引遍历
fmt.Printf(" 使用索引遍历:\n")
for i := 0; i < len(fruits); i++ {
fmt.Printf(" fruits[%d] = %s\n", i, fruits[i])
}
// 使用 range 遍历
fmt.Printf(" 使用 range 遍历:\n")
for index, value := range fruits {
fmt.Printf(" fruits[%d] = %s\n", index, value)
}
// 只要值,忽略索引
fmt.Printf(" 只要值,忽略索引:\n")
for _, fruit := range fruits {
fmt.Printf(" 水果: %s\n", fruit)
}
// 只要索引,忽略值
fmt.Printf(" 只要索引,忽略值:\n")
for index := range fruits {
fmt.Printf(" 索引: %d\n", index)
}
// 数组修改
fmt.Printf(" 数组修改:\n")
numbers := [5]int{1, 2, 3, 4, 5}
fmt.Printf(" 原始数组: %v\n", numbers)
// 修改单个元素
numbers[2] = 99
fmt.Printf(" 修改索引2: %v\n", numbers)
// 批量修改
for i := range numbers {
numbers[i] *= 2
}
fmt.Printf(" 全部乘以2: %v\n", numbers)
// 数组查找
fmt.Printf(" 数组查找:\n")
target := "cherry"
found := false
foundIndex := -1
for i, fruit := range fruits {
if fruit == target {
found = true
foundIndex = i
break
}
}
if found {
fmt.Printf(" 找到 '%s' 在索引 %d\n", target, foundIndex)
} else {
fmt.Printf(" 未找到 '%s'\n", target)
}
fmt.Println()
}// de
monstrateMultiDimensionalArrays 演示多维数组
func demonstrateMultiDimensionalArrays() {
fmt.Println("4. 多维数组:")
// 二维数组
fmt.Printf(" 二维数组:\n")
// 声明和初始化二维数组
var matrix [3][4]int
fmt.Printf(" 声明 3x4 矩阵: %v\n", matrix)
// 初始化二维数组
matrix2 := [2][3]int{
{1, 2, 3},
{4, 5, 6},
}
fmt.Printf(" 初始化 2x3 矩阵:\n")
for i, row := range matrix2 {
fmt.Printf(" 行 %d: %v\n", i, row)
}
// 访问和修改二维数组元素
fmt.Printf(" 访问元素 matrix2[1][2]: %d\n", matrix2[1][2])
matrix2[0][1] = 99
fmt.Printf(" 修改后 matrix2[0][1]: %d\n", matrix2[0][1])
// 遍历二维数组
fmt.Printf(" 遍历二维数组:\n")
for i := 0; i < len(matrix2); i++ {
for j := 0; j < len(matrix2[i]); j++ {
fmt.Printf(" matrix2[%d][%d] = %d\n", i, j, matrix2[i][j])
}
}
// 使用 range 遍历二维数组
fmt.Printf(" 使用 range 遍历:\n")
for i, row := range matrix2 {
for j, value := range row {
fmt.Printf(" [%d][%d] = %d\n", i, j, value)
}
}
// 三维数组
fmt.Printf(" 三维数组:\n")
cube := [2][2][2]int{
{
{1, 2},
{3, 4},
},
{
{5, 6},
{7, 8},
},
}
fmt.Printf(" 3D 数组:\n")
for i, plane := range cube {
fmt.Printf(" 平面 %d:\n", i)
for j, row := range plane {
fmt.Printf(" 行 %d: %v\n", j, row)
}
}
// 实际应用:游戏棋盘
fmt.Printf(" 实际应用 - 井字棋棋盘:\n")
board := [3][3]string{
{"X", "O", "X"},
{"O", "X", "O"},
{"X", "O", "X"},
}
fmt.Printf(" 井字棋棋盘:\n")
for i, row := range board {
fmt.Printf(" ")
for j, cell := range row {
fmt.Printf("%s", cell)
if j < len(row)-1 {
fmt.Printf(" | ")
}
}
fmt.Printf("\n")
if i < len(board)-1 {
fmt.Printf(" ---------\n")
}
}
fmt.Println()
}
// demonstrateArrayProperties 演示数组的特性
func demonstrateArrayProperties() {
fmt.Println("5. 数组的特性:")
// 数组是值类型
fmt.Printf(" 数组是值类型:\n")
arr1 := [3]int{1, 2, 3}
arr2 := arr1 // 拷贝整个数组
fmt.Printf(" 原数组 arr1: %v\n", arr1)
fmt.Printf(" 拷贝数组 arr2: %v\n", arr2)
// 修改拷贝不影响原数组
arr2[0] = 99
fmt.Printf(" 修改 arr2[0] 后:\n")
fmt.Printf(" arr1: %v (未改变)\n", arr1)
fmt.Printf(" arr2: %v (已改变)\n", arr2)
// 数组比较
fmt.Printf(" 数组比较:\n")
a := [3]int{1, 2, 3}
b := [3]int{1, 2, 3}
c := [3]int{1, 2, 4}
fmt.Printf(" a == b: %t\n", a == b)
fmt.Printf(" a == c: %t\n", a == c)
fmt.Printf(" a != c: %t\n", a != c)
// 注意:不同长度的数组是不同类型,不能比较
// var d [4]int
// fmt.Println(a == d) // 编译错误
// 数组作为函数参数
fmt.Printf(" 数组作为函数参数:\n")
original := [3]int{10, 20, 30}
fmt.Printf(" 调用前: %v\n", original)
modifyArrayByValue(original)
fmt.Printf(" 值传递后: %v (未改变)\n", original)
modifyArrayByPointer(&original)
fmt.Printf(" 指针传递后: %v (已改变)\n", original)
// 数组的零值
fmt.Printf(" 数组的零值:\n")
var zeroIntArray [3]int
var zeroStringArray [2]string
var zeroBoolArray [2]bool
fmt.Printf(" int 数组零值: %v\n", zeroIntArray)
fmt.Printf(" string 数组零值: %v\n", zeroStringArray)
fmt.Printf(" bool 数组零值: %v\n", zeroBoolArray)
// 数组长度是编译时常量
fmt.Printf(" 数组长度是编译时常量:\n")
const size = 5
constArray := [size]int{1, 2, 3, 4, 5}
fmt.Printf(" 使用常量定义数组: %v\n", constArray)
fmt.Println()
}
// demonstratePracticalApplications 演示数组的实际应用
func demonstratePracticalApplications() {
fmt.Println("6. 数组的实际应用:")
// 应用1: 统计和分析
fmt.Printf(" 应用1 - 成绩统计:\n")
scores := [10]int{85, 92, 78, 96, 88, 73, 91, 87, 94, 82}
// 计算总分和平均分
total := 0
for _, score := range scores {
total += score
}
average := float64(total) / float64(len(scores))
// 找最高分和最低分
max, min := scores[0], scores[0]
for _, score := range scores {
if score > max {
max = score
}
if score < min {
min = score
}
}
fmt.Printf(" 成绩: %v\n", scores)
fmt.Printf(" 总分: %d\n", total)
fmt.Printf(" 平均分: %.2f\n", average)
fmt.Printf(" 最高分: %d\n", max)
fmt.Printf(" 最低分: %d\n", min)
// 应用2: 频率统计
fmt.Printf(" 应用2 - 字符频率统计:\n")
text := "hello world"
var frequency [26]int // 26个字母的频率
for _, char := range text {
if char >= 'a' && char <= 'z' {
frequency[char-'a']++
}
}
fmt.Printf(" 文本: \"%s\"\n", text)
fmt.Printf(" 字母频率:\n")
for i, count := range frequency {
if count > 0 {
fmt.Printf(" %c: %d\n", 'a'+i, count)
}
}
// 应用3: 查找表
fmt.Printf(" 应用3 - 月份天数查找表:\n")
daysInMonth := [12]int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
months := [12]string{
"一月", "二月", "三月", "四月", "五月", "六月",
"七月", "八月", "九月", "十月", "十一月", "十二月",
}
for i := 0; i < len(months); i++ {
fmt.Printf(" %s: %d天\n", months[i], daysInMonth[i])
}
// 应用4: 缓冲区
fmt.Printf(" 应用4 - 固定大小缓冲区:\n")
buffer := [8]byte{}
data := []byte("Hello")
// 将数据复制到缓冲区
copy(buffer[:], data)
fmt.Printf(" 缓冲区: %v\n", buffer)
fmt.Printf(" 缓冲区内容: %s\n", string(buffer[:len(data)]))
// 应用5: 矩阵运算
fmt.Printf(" 应用5 - 矩阵加法:\n")
matrixA := [2][2]int{{1, 2}, {3, 4}}
matrixB := [2][2]int{{5, 6}, {7, 8}}
var result [2][2]int
// 矩阵加法
for i := 0; i < 2; i++ {
for j := 0; j < 2; j++ {
result[i][j] = matrixA[i][j] + matrixB[i][j]
}
}
fmt.Printf(" 矩阵A: %v\n", matrixA)
fmt.Printf(" 矩阵B: %v\n", matrixB)
fmt.Printf(" A + B: %v\n", result)
fmt.Println()
}
// demonstrateArrayVsSlice 演示数组 vs 切片
func demonstrateArrayVsSlice() {
fmt.Println("7. 数组 vs 切片:")
fmt.Printf(" 数组特点:\n")
fmt.Printf(" - 固定长度,长度是类型的一部分\n")
fmt.Printf(" - 值类型,赋值和传参会拷贝\n")
fmt.Printf(" - 内存效率高,栈上分配\n")
fmt.Printf(" - 编译时确定大小\n")
fmt.Printf(" - 可以直接比较\n")
fmt.Printf(" 切片特点:\n")
fmt.Printf(" - 动态长度,可以增长和缩减\n")
fmt.Printf(" - 引用类型,赋值和传参传递引用\n")
fmt.Printf(" - 更灵活,但有额外开销\n")
fmt.Printf(" - 运行时动态分配\n")
fmt.Printf(" - 不能直接比较\n")
// 性能比较示例
fmt.Printf(" 使用场景建议:\n")
fmt.Printf(" 数组适用于:\n")
fmt.Printf(" - 固定大小的数据集合\n")
fmt.Printf(" - 性能敏感的场景\n")
fmt.Printf(" - 需要值语义的场景\n")
fmt.Printf(" - 小规模数据\n")
fmt.Printf(" 切片适用于:\n")
fmt.Printf(" - 动态大小的数据集合\n")
fmt.Printf(" - 需要灵活操作的场景\n")
fmt.Printf(" - 大规模数据处理\n")
fmt.Printf(" - 函数参数传递\n")
// 转换示例
fmt.Printf(" 数组和切片的转换:\n")
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[:] // 数组转切片
fmt.Printf(" 原数组: %v\n", arr)
fmt.Printf(" 转换的切片: %v\n", slice)
// 修改切片会影响原数组
slice[0] = 99
fmt.Printf(" 修改切片后:\n")
fmt.Printf(" 数组: %v\n", arr)
fmt.Printf(" 切片: %v\n", slice)
fmt.Println()
}
// ========== 辅助函数 ==========
// 值传递数组(会拷贝整个数组)
func modifyArrayByValue(arr [3]int) {
arr[0] = 999
fmt.Printf(" 函数内修改: %v\n", arr)
}
// 指针传递数组(传递数组地址)
func modifyArrayByPointer(arr *[3]int) {
arr[0] = 888
fmt.Printf(" 函数内修改: %v\n", *arr)
}
/*
运行这个程序:
go run 01-arrays.go
学习要点:
1. 数组是固定长度的元素序列,长度是类型的一部分
2. 数组是值类型,赋值和传参会拷贝整个数组
3. 数组的零值是所有元素都为零值的数组
4. 数组可以直接比较(相同类型和长度)
5. 多维数组本质上是数组的数组
数组的特性:
1. 固定长度:编译时确定,不能改变
2. 类型安全:所有元素必须是相同类型
3. 连续内存:元素在内存中连续存储
4. 索引访问O(1) 时间复杂度
5. 值语义:赋值和传参会拷贝
声明和初始化方式:
1. var arr [n]Type
2. arr := [n]Type{values...}
3. arr := [...]Type{values...} // 自动推断长度
4. arr := [n]Type{index: value} // 指定索引
数组 vs 切片:
1. 数组:固定长度,值类型,性能高
2. 切片:动态长度,引用类型,更灵活
使用建议:
1. 小规模、固定大小的数据使用数组
2. 需要动态调整大小时使用切片
3. 性能敏感的场景考虑数组
4. 函数参数通常使用切片而不是数组
常见应用场景:
1. 固定大小的缓冲区
2. 查找表和映射表
3. 矩阵和多维数据
4. 统计和频率分析
5. 游戏棋盘等固定结构
注意事项:
1. 数组长度是类型的一部分
2. 不同长度的数组是不同类型
3. 数组传参会拷贝,大数组考虑传指针
4. 数组索引越界会 panic
5. 多维数组的内存布局是行优先的
*/