数组
定义与初始化
Go 中数组的定义方式非常灵活,支持多种声明和初始化语法。
基本声明
// 声明一个长度为 5 的整型数组,元素默认初始化为零值 0
var arr1 [5]int
fmt.Println(arr1) // [0 0 0 0 0]数组内存布局可视化
64位系统连续内存空间
注:在64位系统上,int类型占用8字节;32位系统上占用4字节
Go 语言中,声明但未显式初始化的变量会被自动赋予该类型的零值。对于数值类型,零值是 0;对于字符串,零值是空字符串 "";对于布尔类型,零值是 false;对于数组,所有元素都会被初始化为其元素类型的零值。
使用字面量初始化
// 方式一:指定所有元素的值
var arr2 = [5]int{1, 2, 3, 4, 5}
fmt.Println(arr2) // [1 2 3 4 5]
// 方式二:使用短变量声明
arr3 := [5]int{10, 20, 30, 40, 50}
fmt.Println(arr3) // [10 20 30 40 50]
// 方式三:让编译器根据初始值个数推断长度(使用 ...)
arr4 := [...]int{1, 2, 3}
fmt.Println(arr4) // [1 2 3]
fmt.Println(len(arr4)) // 3指定索引初始化
// 只初始化指定索引位置的元素,其余为默认零值
arr5 := [5]int{0: 10, 4: 50}
fmt.Println(arr5) // [10 0 0 0 50]
// 用于枚举常量
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
var days = [...]string{Sunday: "周日", Monday: "周一", Tuesday: "周二", Wednesday: "周三", Thursday: "周四", Friday: "周五", Saturday: "周六"}
fmt.Println(days) // [周日 周一 周二 周三 周四 周五 周六]长度是类型的一部分
这是 Go 数组与 C/Java 数组的一个核心区别,也是初学者最容易犯的错误之一。
在 Go 中,[3]int 和 [5]int 是两个完全不同的类型。它们之间不能直接赋值,也不能互相比较。数组的长度在编译期确定,属于数组类型的一部分,不能在运行时改变。
var a [3]int = [3]int{1, 2, 3}
var b [5]int = [5]int{1, 2, 3, 4, 5}
// 以下代码会编译报错:不能将 [5]int 赋值给 [3]int
// a = b // 编译错误:cannot use b (type [5]int) as type [3]int in assignment
// 以下代码也会编译报错:类型不同,不能比较
// fmt.Println(a == b) // 编译错误:invalid operation
fmt.Printf("a 的类型: %T\n", a) // [3]int
fmt.Printf("b 的类型: %T\n", b) // [5]int💡 关键理解:Go 的数组是值类型(Value Type),而非引用类型。当你把一个数组赋值给另一个变量时,会进行一次完整的值拷贝。修改副本不会影响原数组。
original := [3]int{1, 2, 3}
copy := original // 值拷贝:复制了整个数组
copy[0] = 100
fmt.Println(original) // [1 2 3] —— 原数组未被修改
fmt.Println(copy) // [100 2 3]多维数组
Go 支持多维数组,最常用的是二维数组,它可以表示矩阵、表格等数据结构。
声明与初始化
// 声明一个 2 行 3 列的二维数组
var matrix [2][3]int
fmt.Println(matrix) // [[0 0 0] [0 0 0]]
// 使用字面量初始化
grid := [2][3]int{
{1, 2, 3},
{4, 5, 6},
}
fmt.Println(grid) // [[1 2 3] [4 5 6]]
// 访问元素
fmt.Println(grid[0][1]) // 2
fmt.Println(grid[1][2]) // 6
// 修改元素
grid[1][0] = 40
fmt.Println(grid) // [[1 2 3] [40 5 6]]三维及以上
// 3x2x2 的三维数组
cube := [3][2][2]int{
{{1, 2}, {3, 4}},
{{5, 6}, {7, 8}},
{{9, 10}, {11, 12}},
}
fmt.Println(cube[1][0][1]) // 6遍历数组
Go 提供了两种遍历数组的方式:经典 for 循环和 range 循环。
使用 for 循环(通过索引)
arr := [5]int{10, 20, 30, 40, 50}
for i := 0; i < len(arr); i++ {
fmt.Printf("索引 %d: %d\n", i, arr[i])
}
// 输出:
// 索引 0: 10
// 索引 1: 20
// ...使用 range 循环(推荐)
arr := [5]string{"Go", "Python", "Rust", "Java", "C++"}
// 方式一:同时获取索引和值
for index, value := range arr {
fmt.Printf("索引 %d: %s\n", index, value)
}
// 方式二:只获取值(使用空白标识符 _ 忽略索引)
for _, value := range arr {
fmt.Printf("值: %s\n", value)
}
// 方式三:只获取索引
for index := range arr {
fmt.Printf("索引: %d\n", index)
}遍历多维数组
matrix := [2][3]int{
{1, 2, 3},
{4, 5, 6},
}
for i, row := range matrix {
for j, val := range row {
fmt.Printf("matrix[%d][%d] = %d\n", i, j, val)
}
}💡 range 的副本特性:range 在遍历时,value 是数组元素的副本。如果你需要修改原数组元素,必须通过索引来操作:arr[i] = newValue,而不是直接修改 value。
arr := [3]int{1, 2, 3}
for _, v := range arr {
v = v * 10 // 修改的是副本,不影响原数组
}
fmt.Println(arr) // [1 2 3] —— 未被修改
// 正确的修改方式
for i := range arr {
arr[i] = arr[i] * 10
}
fmt.Println(arr) // [10 20 30]数组作为函数参数
因为数组是值类型,所以将数组传递给函数时会进行完整的拷贝。对于大数组来说,这会带来性能开销。
func modifyArray(arr [3]int) {
arr[0] = 999 // 修改的是函数内的副本
}
func main() {
original := [3]int{1, 2, 3}
modifyArray(original)
fmt.Println(original) // [1 2 3] —— 原数组未被修改
}如果希望在函数内修改原数组,可以传递数组的指针:
func modifyArrayByPointer(arr *[3]int) {
arr[0] = 999 // 通过指针修改原数组
}
func main() {
original := [3]int{1, 2, 3}
modifyArrayByPointer(&original)
fmt.Println(original) // [999 2 3] —— 原数组已被修改
}数组的局限性
Go 中的数组在实际开发中直接使用频率不高,主要存在以下局限性:
| 局限性 | 说明 |
|---|---|
| 长度固定 | 数组一旦声明,长度不可改变,缺乏灵活性 |
| 长度是类型的一部分 | 不同长度的数组是不同类型,难以编写通用的函数 |
| 值拷贝开销 | 作为参数传递时会完整拷贝,对大数组性能不友好 |
| 无法动态扩缩 | 无法在运行时增加或减少元素 |
✅ 实际开发建议:在 Go 中,大多数场景下应使用**切片(Slice)**代替数组。切片提供了动态长度、自动扩容等灵活特性,是 Go 中最常用的序列类型。但我们仍然需要理解数组,因为它是切片的底层基础,也是理解 Go 内存模型的关键。
练习题
练习 1:数组基本操作
编写一个程序,声明一个长度为 10 的 int 数组,将数组中的每个元素设置为其索引的平方(即 arr[i] = i * i),然后打印整个数组。
package main
import "fmt"
func main() {
var squares [10]int
for i := 0; i < len(squares); i++ {
squares[i] = i * i
}
fmt.Println(squares) // [0 1 4 9 16 25 36 49 64 81]
}练习 2:矩阵转置
编写一个函数,接收一个 3×3 的 int 矩阵(二维数组),返回其转置矩阵(行列互换)。
package main
import "fmt"
func transpose(matrix [3][3]int) [3][3]int {
var result [3][3]int
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
result[i][j] = matrix[j][i]
}
}
return result
}
func main() {
matrix := [3][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
fmt.Println("原始矩阵:")
for _, row := range matrix {
fmt.Println(row)
}
result := transpose(matrix)
fmt.Println("\n转置矩阵:")
for _, row := range result {
fmt.Println(row)
}
}
// 输出:
// 原始矩阵:
// [1 2 3]
// [4 5 6]
// [7 8 9]
//
// 转置矩阵:
// [1 4 7]
// [2 5 8]
// [3 6 9]练习 3:数组值传递验证
编写一个程序验证数组是值类型:创建一个数组,赋值给另一个变量,修改其中一个变量的元素,观察另一个变量是否受影响。
package main
import "fmt"
func main() {
arr1 := [4]string{"春", "夏", "秋", "冬"}
arr2 := arr1 // 值拷贝
arr2[0] = "Spring"
fmt.Println("arr1:", arr1) // arr1: [春 夏 秋 冬]
fmt.Println("arr2:", arr2) // arr2: [Spring 夏 秋 冬]
// arr1 不受 arr2 修改的影响,证明数组是值类型
}