Initial commit
This commit is contained in:
@@ -0,0 +1,440 @@
|
||||
# Go 语言数组详解:从基础到实践
|
||||
|
||||
在 Go 语言中,**数组(Array)** 是最基本、最底层的数据结构之一,虽然在实际开发中我们更多使用切片(Slice),但理解数组对于深入掌握 Go 的内存模型、值传递机制和性能优化至关重要。
|
||||
|
||||
---
|
||||
|
||||
## 1. 数组基础概念
|
||||
|
||||
数组是 Go 中一种**固定长度、类型一致、有序存储**的集合类型。
|
||||
|
||||
### 核心特征
|
||||
|
||||
- ✅ **类型一致性**:所有元素必须是相同类型
|
||||
- ✅ **固定长度**:声明后长度不可变(编译期确定)
|
||||
- ✅ **值类型**:赋值和传参时会进行**深拷贝**
|
||||
- ✅ **连续内存布局**:元素在内存中连续存放,有利于缓存命中
|
||||
|
||||
> ⚠️ 注意:`[3]int` 和 `[5]int` 是两种完全不同的类型!
|
||||
|
||||
---
|
||||
|
||||
## 2. 数组声明与初始化
|
||||
|
||||
### 示例 1:基本声明与初始化
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
var arr1 [5]int // 声明但未初始化,元素默认为零值
|
||||
var arr2 = [5]int{1, 2, 3, 4, 5} // 显式初始化
|
||||
arr3 := [5]int{10, 20, 30, 40, 50} // 简短声明
|
||||
|
||||
fmt.Println("arr1:", arr1) // [0 0 0 0 0]
|
||||
fmt.Println("arr2:", arr2) // [1 2 3 4 5]
|
||||
fmt.Println("arr3:", arr3) // [10 20 30 40 50]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 示例 2:自动推导长度 `…`
|
||||
|
||||
Go 支持使用 `…` 让编译器自动推导数组长度。
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
arr := [...]int{1, 2, 3, 4, 5} // 自动推断为 [5]int
|
||||
fmt.Println("数组长度:", len(arr)) // 输出:5
|
||||
fmt.Println("数组内容:", arr) // [1 2 3 4 5]
|
||||
|
||||
// 注意:一旦推导完成,长度依然是固定的
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 示例 3:指定索引初始化(稀疏数组风格)
|
||||
|
||||
可以跳过某些位置初始化,未指定的位置将使用零值填充。
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
arr := [10]int{1: 100, 5: 500} // 第1和第5个元素被赋值
|
||||
fmt.Println("指定索引初始化:", arr) // [0 100 0 0 0 500 0 0 0 0]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 示例 4:复合类型的数组(结构体为例)
|
||||
|
||||
数组不仅能存基本类型,还能存储结构体、指针、数组等复合类型。
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Student struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
func main() {
|
||||
students := [2]Student{
|
||||
{"Alice", 20},
|
||||
{"Bob", 22},
|
||||
}
|
||||
|
||||
fmt.Println("学生数组:", students) // [{Alice 20} {Bob 22}]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 数组操作详解
|
||||
|
||||
### 示例 5:获取长度与容量
|
||||
|
||||
数组的 `len()` 和 `cap()` 返回值总是相等,因为容量 == 长度。
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
arr := [5]string{"a", "b", "c"}
|
||||
fmt.Printf("长度: %d\n", len(arr)) // 5
|
||||
fmt.Printf("容量: %d\n", cap(arr)) // 5
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 示例 6:安全访问与修改元素
|
||||
|
||||
越界访问会导致 panic!
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
arr := [3]int{10, 20, 30}
|
||||
|
||||
// 访问
|
||||
fmt.Println("第一个元素:", arr[0]) // 10
|
||||
|
||||
// 修改
|
||||
arr[1] = 99
|
||||
fmt.Println("修改后:", arr) // [10 99 30]
|
||||
|
||||
// ❌ 错误示例(会 panic)—— 超出范围
|
||||
// arr[5] = 100 // panic: runtime error: index out of range
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 示例 7:遍历数组的三种方式
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
arr := [4]int{1, 3, 5, 7}
|
||||
|
||||
fmt.Println("1. 使用索引遍历:")
|
||||
for i := 0; i < len(arr); i++ {
|
||||
fmt.Printf("arr[%d] = %d\n", i, arr[i])
|
||||
}
|
||||
|
||||
fmt.Println("\n2. 使用 range 索引:")
|
||||
for i := range arr {
|
||||
fmt.Printf("arr[%d] = %d\n", i, arr[i])
|
||||
}
|
||||
|
||||
fmt.Println("\n3. 使用 range 获取索引和值:")
|
||||
for idx, val := range arr {
|
||||
fmt.Printf("索引 %d => 值 %d\n", idx, val)
|
||||
}
|
||||
|
||||
fmt.Println("\n4. 忽略索引,只获取值:")
|
||||
for _, val := range arr {
|
||||
fmt.Printf("值: %d ", val)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 多维数组详解
|
||||
|
||||
### 示例 8:二维数组的声明与遍历
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
// 声明一个 3x4 的二维数组
|
||||
matrix := [3][4]int{
|
||||
{0, 1, 2, 3},
|
||||
{4, 5, 6, 7},
|
||||
{8, 9, 10, 11},
|
||||
}
|
||||
|
||||
fmt.Println("二维数组内容:")
|
||||
for i := 0; i < len(matrix); i++ {
|
||||
for j := 0; j < len(matrix[i]); j++ {
|
||||
fmt.Printf("%4d", matrix[i][j])
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 输出:
|
||||
|
||||
```
|
||||
0 1 2 3
|
||||
4 5 6 7
|
||||
8 9 10 11
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 示例 9:三维及以上数组(了解)
|
||||
|
||||
虽然不常用,但 Go 支持高维数组。
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
cube := [2][3][2]int{
|
||||
{{1, 2}, {3, 4}, {5, 6}},
|
||||
{{7, 8}, {9, 10}, {11, 12}},
|
||||
}
|
||||
|
||||
fmt.Println("三维数组 [0][1][1]:", cube[0][1][1]) // 输出:4
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 数组作为值类型的特性
|
||||
|
||||
这是 Go 数组最重要的特性之一:**值类型 vs 引用类型**
|
||||
|
||||
### 示例 10:数组赋值产生副本
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
arr1 := [4]int{1, 2, 3, 4}
|
||||
arr2 := arr1 // 完整复制一份(深拷贝)
|
||||
|
||||
arr2[0] = 999
|
||||
|
||||
fmt.Println("arr1:", arr1) // [1 2 3 4] — 不受影响
|
||||
fmt.Println("arr2:", arr2) // [999 2 3 4]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 示例 11:函数传参时的复制行为
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func modify(arr [3]int) {
|
||||
arr[0] = 999
|
||||
fmt.Println("函数内 arr:", arr) // [999 2 3]
|
||||
}
|
||||
|
||||
func main() {
|
||||
original := [3]int{1, 2, 3}
|
||||
modify(original)
|
||||
fmt.Println("函数外 original:", original) // [1 2 3] — 未改变!
|
||||
}
|
||||
```
|
||||
|
||||
> ✅ 解决方案:若需修改原始数据,应传指针:
|
||||
|
||||
```go
|
||||
func modifyPtr(arr *[3]int) {
|
||||
arr[0] = 999 // 等价于 (*arr)[0]
|
||||
}
|
||||
|
||||
// 调用:modifyPtr(&original)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 数组的常见陷阱与注意事项
|
||||
|
||||
### ❌ 陷阱 1:不同长度的数组不能赋值
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
func main() {
|
||||
// var a [3]int
|
||||
// var b [4]int
|
||||
// a = b // 编译错误:cannot use b (type [4]int) as type [3]int
|
||||
}
|
||||
```
|
||||
|
||||
虽然都是整型数组,但 `[3]int` ≠ `[4]int`,属于不同类型。
|
||||
|
||||
---
|
||||
|
||||
### ❌ 陷阱 2:大型数组传递性能差
|
||||
|
||||
如果数组很大(如 `[10000]int`),每次传参会复制整个数组,造成严重性能问题。
|
||||
|
||||
✅ 正确做法:使用指针。
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func process(data *[1000]int) {
|
||||
// 直接操作原数据,无复制开销
|
||||
data[0] = 100
|
||||
}
|
||||
|
||||
func main() {
|
||||
largeArr := [1000]int{}
|
||||
process(&largeArr)
|
||||
fmt.Println("largeArr[0]:", largeArr[0]) // 100
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ❌ 陷阱 3:无法动态扩容
|
||||
|
||||
数组长度固定,不能追加元素。
|
||||
|
||||
✅ 替代方案:使用切片(slice),它是对数组的抽象封装。
|
||||
|
||||
```go
|
||||
slice := []int{1, 2, 3}
|
||||
slice = append(slice, 4) // ✅ 可以动态增长
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 最佳实践与性能建议
|
||||
|
||||
| 实践 | 说明 |
|
||||
|------|------|
|
||||
| ✅ 优先使用切片 | 绝大多数场景下应使用 `[]int` 而非 `[5]int` |
|
||||
| ✅ 小数组可用值传递 | 若长度小(如 `[3]float64` 表示坐标),值传递更安全高效 |
|
||||
| ✅ 大数组用指针传递 | 避免不必要的复制开销 |
|
||||
| ✅ 利用连续内存优势 | 数组适合科学计算、图像处理等需缓存友好的场景 |
|
||||
| ✅ 多维数组注意初始化格式 | 特别是混合维度时容易出错 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 典型应用场景
|
||||
|
||||
### 📌 场景 1:固定配置参数
|
||||
|
||||
如 RGB 颜色表示:
|
||||
|
||||
```go
|
||||
var color [3]byte = [3]byte{255, 0, 128}
|
||||
```
|
||||
|
||||
### 📌 场景 2:哈希表键(map key)
|
||||
|
||||
数组如果是可比较类型,可作为 map 的 key,而切片不能。
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
// 使用 [2]int 作为 map 键
|
||||
coordMap := make(map[[2]int]string)
|
||||
coordMap[[2]int{0, 0}] = "origin"
|
||||
coordMap[[2]int{3, 4}] = "point A"
|
||||
|
||||
fmt.Println(coordMap) // map[[0 0]:origin [3 4]:point A]
|
||||
}
|
||||
```
|
||||
|
||||
> ✅ 注意:只有**可比较类型**的数组才能做 key(如 `[2]int`),包含 `slice`、`map`、`func` 的数组不行。
|
||||
|
||||
---
|
||||
|
||||
### 📌 场景 3:性能敏感的底层计算
|
||||
|
||||
如矩阵运算、密码学哈希中的定长缓冲区:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := []byte("hello")
|
||||
hash := sha256.Sum256(data) // 返回 [32]byte 类型
|
||||
fmt.Printf("SHA256: %x\n", hash)
|
||||
fmt.Printf("类型是: %T\n", hash) // [32]uint8
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
| 关键点 | 说明 |
|
||||
|--------|------|
|
||||
| 🧩 数组是 Go 的基础结构 | 了解它是理解切片的前提 |
|
||||
| 🔐 值类型带来安全性 | 避免意外修改,但也带来开销 |
|
||||
| ⚡ 连续内存提升性能 | 适合高性能计算场景 |
|
||||
| 🚫 无法扩容 | 日常开发建议用 `slice` 代替 |
|
||||
| 💡 适用于小而固定的集合 | 如坐标、颜色、哈希值等 |
|
||||
|
||||
> ✅ **推荐口诀**:
|
||||
> 小而固定用数组,大而可变用切片;传参小心复制坑,指针优化性能佳。
|
||||
Reference in New Issue
Block a user