Go 编程实战:数组 Array——固定长度的数据集合

在前面的文章中,我们已经学习了 Go 语言中的 breakcontinuegoto,掌握了如何控制循环执行流程。从这一篇开始,我们进入 Go 中非常重要的数据结构部分。

在实际开发中,我们经常需要保存一组相同类型的数据。例如保存学生成绩、服务器端口、商品价格、用户 ID 等。如果为每一个数据单独定义变量,不仅代码繁琐,而且不方便统一处理。

这时候就可以使用数组 Array。

数组是 Go 语言最基础的集合类型之一,也是后面学习 Slice、Map 等数据结构的重要基础。

一、什么是数组

数组是一组长度固定、类型相同的数据集合。

基本语法:

css 复制代码
var 数组名 [长度]数据类型

例如:

csharp 复制代码
var nums [5]int

表示定义一个长度为 5 的 int 数组。

可以理解为:

diff 复制代码
nums
+----+----+----+----+----+
| 0  | 0  | 0  | 0  | 0  |
+----+----+----+----+----+
  0    1    2    3    4

数组中的每个位置都有一个下标,下标从 0 开始。

长度为 5 的数组,其有效下标为:

复制代码
0
1
2
3
4

因此最后一个元素的下标永远是:

scss 复制代码
len(nums) - 1

二、数组的零值

Go 语言变量在声明后即使没有手动赋值,也会自动获得对应类型的零值。

例如:

go 复制代码
package main

import "fmt"

func main() {
	var nums [5]int

	fmt.Println(nums)
}

输出:

csharp 复制代码
[0 0 0 0 0]

如果是字符串数组:

csharp 复制代码
var names [3]string
fmt.Println(names)

结果中的每个元素都是空字符串。

布尔数组:

csharp 复制代码
var flags [3]bool
fmt.Println(flags)

输出:

arduino 复制代码
[false false false]

因此 Go 中定义数组后可以直接使用,不需要手动初始化所有元素。

三、给数组元素赋值

通过下标可以访问和修改数组中的元素:

css 复制代码
package main

import "fmt"

func main() {
	var scores [5]int

	scores[0] = 90
	scores[1] = 85
	scores[2] = 96
	scores[3] = 78
	scores[4] = 88

	fmt.Println(scores)
}

输出:

csharp 复制代码
[90 85 96 78 88]

也可以单独访问某个元素:

css 复制代码
fmt.Println(scores[0])
fmt.Println(scores[2])

输出:

复制代码
90
96

数组下标不能超过有效范围,例如:

css 复制代码
var nums [5]int

nums[5] = 100

这里会出现越界问题,因为 [5]int 的有效下标只有 0~4

四、定义数组时直接初始化

除了先声明再赋值,还可以在创建数组的时候直接初始化。

go 复制代码
nums := [5]int{10, 20, 30, 40, 50}

fmt.Println(nums)

输出:

csharp 复制代码
[10 20 30 40 50]

字符串数组:

go 复制代码
names := [3]string{"Tom", "Jack", "Lucy"}

如果没有提供所有元素:

go 复制代码
nums := [5]int{10, 20}

实际上相当于:

csharp 复制代码
[10 20 0 0 0]

剩余元素自动使用零值。

五、让 Go 自动计算数组长度

如果初始化数据比较多,可以使用:

go 复制代码
nums := [...]int{10, 20, 30, 40, 50}

这里的:

erlang 复制代码
...

表示让编译器自动计算数组长度。

因此:

go 复制代码
nums := [...]int{10, 20, 30}

等价于:

go 复制代码
nums := [3]int{10, 20, 30}

可以使用 len() 查看长度:

go 复制代码
fmt.Println(len(nums))

输出:

复制代码
3

这种写法在初始化数组时非常方便。

六、指定下标初始化

Go 还支持通过下标初始化数组。

例如:

yaml 复制代码
nums := [5]int{
	0: 10,
	2: 30,
	4: 50,
}

fmt.Println(nums)

输出:

csharp 复制代码
[10 0 30 0 50]

没有指定的位置仍然使用零值。

也可以写成:

css 复制代码
nums := [...]int{
	2: 100,
	5: 200,
}

因为最大下标是 5,所以数组长度会自动推导为 6

七、遍历数组

数组最常见的操作之一就是遍历。

可以使用普通 for 循环:

go 复制代码
package main

import "fmt"

func main() {
	nums := [5]int{10, 20, 30, 40, 50}

	for i := 0; i < len(nums); i++ {
		fmt.Println(nums[i])
	}
}

这种方式适合需要使用下标的场景。

Go 更推荐使用 range

go 复制代码
for index, value := range nums {
	fmt.Println(index, value)
}

输出:

复制代码
0 10
1 20
2 30
3 40
4 50

其中:

diff 复制代码
index

表示当前下标,

复制代码
value

表示当前元素。

如果只需要值:

go 复制代码
for _, value := range nums {
	fmt.Println(value)
}

如果只需要下标:

go 复制代码
for index := range nums {
	fmt.Println(index)
}

八、修改数组中的元素

数组中的元素是可以修改的:

css 复制代码
nums := [3]int{10, 20, 30}

nums[1] = 100

fmt.Println(nums)

输出:

csharp 复制代码
[10 100 30]

也可以通过循环修改:

css 复制代码
for i := 0; i < len(nums); i++ {
	nums[i] *= 2
}

例如:

css 复制代码
nums := [5]int{1, 2, 3, 4, 5}

for i := 0; i < len(nums); i++ {
	nums[i] *= 2
}

fmt.Println(nums)

输出:

csharp 复制代码
[2 4 6 8 10]

需要注意,如果这样写:

go 复制代码
for _, value := range nums {
	value *= 2
}

原数组不会改变。

因为这里的 value 是数组元素值的副本,而不是原元素本身。

九、数组的长度属于类型的一部分

这是 Go 数组非常重要的特点。

下面两个变量:

css 复制代码
var a [3]int
var b [5]int

虽然它们存储的都是 int,但是类型并不相同。

一个是:

csharp 复制代码
[3]int

另一个是:

csharp 复制代码
[5]int

因此不能直接:

css 复制代码
a = b

这也是 Go 数组和很多其他语言数组之间比较明显的区别。

数组长度在编译阶段就已经确定,运行过程中不能改变。

十、数组之间可以比较

如果数组元素类型支持比较,那么相同类型的数组也可以直接使用:

复制代码
==

和:

diff 复制代码
!=

例如:

css 复制代码
a := [3]int{1, 2, 3}
b := [3]int{1, 2, 3}
c := [3]int{1, 2, 4}

fmt.Println(a == b)
fmt.Println(a == c)

输出:

arduino 复制代码
true
false

只有数组长度、元素类型和每个位置的值都相同,两个数组才相等。

十一、数组是值类型

这是学习 Go 数组必须掌握的重点。

Go 中数组属于值类型

例如:

css 复制代码
a := [3]int{10, 20, 30}

b := a

b[0] = 100

fmt.Println(a)
fmt.Println(b)

输出:

csharp 复制代码
[10 20 30]
[100 20 30]

为什么修改 b 不影响 a

因为:

css 复制代码
b := a

会复制整个数组。

此时可以理解为:

css 复制代码
a -> [10 20 30]

b -> [10 20 30]

它们是两份独立的数据。

这与后面要学习的 Slice 有很大的区别。

十二、数组作为函数参数

因为数组是值类型,所以把数组传递给函数时,默认也是复制。

例如:

go 复制代码
package main

import "fmt"

func change(nums [3]int) {
	nums[0] = 100
}

func main() {
	nums := [3]int{10, 20, 30}

	change(nums)

	fmt.Println(nums)
}

输出:

csharp 复制代码
[10 20 30]

函数内部修改的是复制后的数组,因此不会影响原数组。

如果确实需要修改原数组,可以传递数组指针:

go 复制代码
func change(nums *[3]int) {
	nums[0] = 100
}

调用:

scss 复制代码
change(&nums)

完整示例:

go 复制代码
package main

import "fmt"

func change(nums *[3]int) {
	nums[0] = 100
}

func main() {
	nums := [3]int{10, 20, 30}

	change(&nums)

	fmt.Println(nums)
}

输出:

csharp 复制代码
[100 20 30]

不过实际 Go 项目中,如果需要频繁向函数传递一组数据,通常不会直接传数组,而是使用下一篇要学习的 Slice。

十三、二维数组

数组中的元素本身也可以是数组。

例如:

css 复制代码
var nums [3][4]int

表示:

复制代码
3 行
4 列

可以理解为:

csharp 复制代码
[
 [0 0 0 0]
 [0 0 0 0]
 [0 0 0 0]
]

初始化:

go 复制代码
nums := [3][4]int{
	{1, 2, 3, 4},
	{5, 6, 7, 8},
	{9, 10, 11, 12},
}

访问第二行第三个元素:

css 复制代码
fmt.Println(nums[1][2])

结果:

复制代码
7

遍历二维数组:

css 复制代码
for i := 0; i < len(nums); i++ {
	for j := 0; j < len(nums[i]); j++ {
		fmt.Printf("%d ", nums[i][j])
	}
	fmt.Println()
}

也可以使用 range

go 复制代码
for _, row := range nums {
	for _, value := range row {
		fmt.Printf("%d ", value)
	}
	fmt.Println()
}

二维数组可以用于矩阵、棋盘、表格数据等场景。

十四、数组实战:计算成绩

例如保存 5 名学生的成绩:

python 复制代码
package main

import "fmt"

func main() {
	scores := [5]int{86, 92, 78, 95, 88}

	sum := 0
	max := scores[0]
	min := scores[0]

	for _, score := range scores {
		sum += score

		if score > max {
			max = score
		}

		if score < min {
			min = score
		}
	}

	avg := float64(sum) / float64(len(scores))

	fmt.Println("总分:", sum)
	fmt.Println("平均分:", avg)
	fmt.Println("最高分:", max)
	fmt.Println("最低分:", min)
}

输出类似:

复制代码
总分: 439
平均分: 87.8
最高分: 95
最低分: 78

通过这个例子可以看到,数组特别适合保存数量已经确定的一组相同类型的数据。

十五、数组适合什么场景

数组最大的特点就是:

复制代码
长度固定

因此比较适合数量明确且不会变化的数据。

例如:

csharp 复制代码
var week [7]string

表示一周七天。

csharp 复制代码
var months [12]int

表示一年十二个月的数据。

或者:

go 复制代码
rgb := [3]int{255, 128, 0}

表示 RGB 三个颜色通道。

但是现实项目中的数据数量通常是不固定的。

例如用户列表可能今天 100 个用户,明天 1000 个用户;商品列表可能不断增加;接口返回的数据数量也无法提前确定。

这时候固定长度数组就显得不够灵活。

十六、数组常见错误

第一个常见问题是数组越界:

css 复制代码
nums := [3]int{1, 2, 3}

fmt.Println(nums[3])

数组只有:

复制代码
0
1
2

三个有效下标,因此访问 3 会越界。

第二个问题是认为不同长度数组属于相同类型:

css 复制代码
var a [3]int
var b [5]int

a = b

这是不允许的,因为 [3]int[5]int 是两个不同的类型。

第三个问题是误以为数组赋值共享数据:

css 复制代码
a := [3]int{1, 2, 3}
b := a

b[0] = 100

这里不会修改 a,因为数组赋值会复制数据。

十七、数组与 Slice 的关系

学习数组之后,很快就会发现一个问题:

csharp 复制代码
var nums [5]int

长度一旦确定就不能改变。

但是实际开发中的集合通常需要:

复制代码
动态增加数据
动态删除数据
动态截取数据
向函数传递任意长度的数据

如果全部使用数组,实现起来会非常不方便。

因此 Go 提供了更加常用的数据结构:

复制代码
Slice

也就是切片

切片底层和数组有非常密切的关系,但是相比数组更加灵活。

例如:

go 复制代码
nums := []int{10, 20, 30}

注意这里没有数组长度:

arduino 复制代码
[]int

它表示的就不再是数组,而是一个 int 类型的切片。

切片还可以动态追加元素:

ini 复制代码
nums = append(nums, 40)
nums = append(nums, 50)

这是数组无法直接做到的。

因此在实际 Go 项目中:

复制代码
数组是基础
Slice 才是更常用的数据集合

理解数组的值类型、固定长度、下标访问以及内存结构之后,再学习 Slice 会容易很多。

总结

数组 Array 是 Go 语言中用于保存固定数量相同类型数据的基础数据结构。

定义数组:

csharp 复制代码
var nums [5]int

初始化:

go 复制代码
nums := [5]int{1, 2, 3, 4, 5}

自动推导长度:

go 复制代码
nums := [...]int{1, 2, 3}

访问元素:

css 复制代码
nums[0]

获取长度:

scss 复制代码
len(nums)

遍历:

go 复制代码
for index, value := range nums {
	fmt.Println(index, value)
}

需要重点记住三个特点:

markdown 复制代码
1. 数组长度固定
2. 数组长度属于类型的一部分
3. 数组属于值类型,赋值和函数传参默认会复制

数组虽然在实际业务开发中的直接使用频率没有 Slice 高,但它是理解 Go 集合数据结构的重要基础。特别是 Slice 的底层实现与数组密切相关,因此数组这一部分一定要理解清楚。

下一篇:切片 Slice------Go 中最常用的动态数据集合

相关推荐
ShineWinsu9 小时前
对于C++:auto_ptr、unique_ptr、shared_ptr的模拟实现
c++·面试·笔试·智能指针·unique_ptr·shared_ptr·auto_ptr
桦说编程9 小时前
并发编程中的等待-通知模式:从 wait/notify 到 Guava Monitor
后端
用户9385156350711 小时前
工厂模式与 Nest.js 核心思想 —— 从蜜雪冰城到企业级架构
后端·设计模式·nestjs
用户9385156350711 小时前
实战 Todo CRUD —— 从路由到异常,手写一个完整模块
后端·typescript·nestjs
GetcharZp11 小时前
Qdrant 向量数据库 Golang 实战指南:从零构建高性能检索应用
后端
黄敬峰11 小时前
一文搞懂 NestJS 后端框架:工厂模式、模块化与装饰器
面试
hsfxuebao11 小时前
OpenSpec 完整使用流程笔记 (SDD)
后端
唐青枫12 小时前
别把 struct 只当成数据盒子:Zig 结构体从入门到实战
后端
why技术12 小时前
AI 写的文章,可能都带着手敲一遍都去不掉的“隐形水印”。
前端·人工智能·后端