← 目录 / 第六章 · 数组 / 6.1 一维数组

6.1 一维数组

数组是存储相同类型数据的容器,像一排连续的格子,是几乎所有算法题都会用到的基础数据结构。

一维数组

数组(array)是一种线性数据结构,将相同类型的元素存储在连续的内存空间中。元素在数组中的位置称为索引(index),索引从 0 开始。

数组
1
3
2
5
4
← 连续内存
索引
0
1
2
3
4
内存地址
0x00
0x04
0x08
0x0C
0x10
元素地址 = 数组首地址 + 元素字节数 × 索引 例:索引3的地址 = 0x00 + 4 × 3 = 0x0C
📦
连续存储的意义:正因为数组元素紧挨着存放,只要知道数组的首地址、每个元素占几个字节,就能瞬间算出任意索引对应的内存地址(见上方公式),这也是数组"随机访问"速度极快的根本原因——不管访问第 0 个还是第 999 个元素,花的时间几乎完全一样。

6.1.1 数组命名规则

规则合法示例不合法示例原因
只能含字母、数字、下划线scores, my_arrmy-arr含连字符
不能以数字开头arr22arr数字开头
不能与已定义变量重名myArrint n=5; int n[10];名字冲突
不能使用关键字forArrint for[5];for 是关键字

6.1.2 定义与初始化

C++ · 数组定义与初始化
1// 语法:类型 数组名[大小];
2int a[5]; // 定义可存 5 个整数的数组(未初始化)
3
4int b[5] = {90, 85, 78, 92, 88}; // 完整初始化
5int c[] = {90, 85, 78, 92, 88}; // 省略大小,自动推断为 5
6int d[5] = {1, 2}; // 部分初始化 → {1, 2, 0, 0, 0}
7
8a = b; // ❌ 错误!数组变量之间不能直接赋值
🏗️
大数组放全局!main() 内的局部数组存放在栈(Stack)中,栈空间默认仅 1~2 MB。数组过大会立即导致栈溢出(程序崩溃),且无任何警告,是极隐蔽的错误。
解决方法:将大数组定义在 main() 外部(全局变量),它会被分配到静态存储区,空间充裕。

访问数组中的元素

数组下标从 0 开始,最后一个元素的下标是 大小 - 1。通过 数组名[下标] 访问或修改指定位置的元素。

C++ · 数组访问与遍历
1int a[5] = {90, 85, 78, 92, 88};
2//下标: 0 1 2 3 4
3
4cout << a[0]; // 输出:90(第1个元素)
5cout << a[4]; // 输出:88(最后一个,下标 = 大小-1)
6a[2] = 99; // 修改第3个元素
7
8a[3] = {0,1,2}; // ❌ 错误!非定义时不能整体赋值
9
10// 用循环遍历数组
11for (int i = 0; i < 5; i++)
12 cout << a[i] << " ";
💥
下标越界!C++ 不会检查下标是否合法。访问 a[5](越界)不会报错,但会读写到相邻内存,造成程序崩溃或数据错误,是极危险的隐藏 bug。始终确保下标在 [0, 大小-1] 范围内。

插入元素

数组元素在内存中紧挨着,中间没有空间。在索引 1 处插入元素 3 时,需要先将该位置及之后的所有元素依次向后移动一位,再写入新值。由于数组长度固定,插入会导致尾部元素丢失。

示例:在索引 1 处插入元素 3,数组 = {1,2,5,4,0}
1
2
5
4
0
← 初始状态(有效元素 4 个,末位 0 无意义)
↓ nums[4] = nums[3]
1
2
5
4
4
← 索引3的值复制到索引4
↓ nums[3] = nums[2]
1
2
5
5
4
← 索引2的值复制到索引3
↓ nums[2] = nums[1]
1
2
2
5
4
← 索引1的值复制到索引2
↓ nums[1] = 3
1
3
2
5
4
← 插入完成!初始末尾元素 0 已丢失
C++ · 数组插入元素
1int index = 1, num = 3;
2int nums[5] = {1,2,5,4,0};
3
4// 从后往前移动,腾出 index 位置
5for (int i = 5 - 1; i > index; i--)
6 nums[i] = nums[i - 1];
7
8nums[index] = num; // 写入新值
⬅️
为什么要从后往前移?如果从前往后移(nums[2]=nums[1] 先执行),会把索引2原本的值提前覆盖掉,还没来得及挪到索引3就已经丢失了。从后往前移动能保证每次覆盖之前,被覆盖位置的值已经被读取并转移走。

删除元素

删除索引 1 处的元素时,需要把该位置之后的所有元素依次向前移动一位。删除后末尾元素无意义,有效长度减 1。

示例:删除索引 1 处的元素 3,数组 = {1,3,2,5,4}
1
3
2
5
4
← 待删除元素(索引1)
↓ nums[1] = nums[2]
1
2
2
5
4
← 索引2的值覆盖索引1
↓ nums[2] = nums[3]
1
2
5
5
4
← 索引3的值覆盖索引2
↓ nums[3] = nums[4]
1
2
5
4
4
← 删除完成!末尾 4 已无意义,有效长度变为 4
C++ · 数组删除元素
1int index = 1;
2int nums[5] = {1,3,2,5,4};
3
4// 从前往后移动,覆盖 index 位置
5for (int i = index; i < 5 - 1; i++)
6 nums[i] = nums[i + 1]; // 后一个覆盖前一个
7// 末尾元素已无意义,有效长度 -1
🔄
插入 vs 删除,移动方向正好相反:插入时从后往前移动(腾空间),删除时从前往后移动(填空隙)。方向搞反是这两节最容易犯的错误,可以记成"插入让位要倒着来,删除补位要顺着走"。

常用操作与排序

C++ · 求最值、求和、逆序输出
1int a[] = {3,1,4,1,5,9,2,6}, n = 8;
2
3// ── 求最大值 ──
4int maxVal = a[0]; // 以第一个元素为初始最大值
5for (int i = 1; i < n; i++)
6 if (a[i] > maxVal) maxVal = a[i];
7
8// ── 求总和与平均值 ──
9int sum = 0;
10for (int i = 0; i < n; i++) sum += a[i];
11double avg = (double)sum / n; // 见 2.1 节:先转 double 再除
12
13// ── 逆序输出 ──
14for (int i = n-1; i >= 0; i--) cout << a[i] << " ";

sort() 排序(需 #include <algorithm>

C++ · sort() 排序
1#include <algorithm>
2int a[] = {3, 1, 4, 1, 5, 9}, n = 6;
3
4sort(a, a + n); // 升序:1 1 3 4 5 9
5sort(a, a + n, greater<int>()); // 降序:9 5 4 3 1 1
💡
sort 的参数含义:sort(a, a + n) 中的 a 是数组首地址,a + n 是"最后一个有效元素之后"的地址(左闭右开区间),这是 C++ 标准库的通用约定,后续学习 STL 容器时会反复见到同样的写法。

常见陷阱与易错点

把本节内容汇总成几条最容易踩坑的规则:

1
下标越界不会报错:C++ 不检查下标合法性,访问越界的 a[n]a[-1] 不会立刻崩溃,而是悄悄读写了相邻内存,可能过一会儿才在完全无关的地方引发诡异的错误,非常难排查。
2
局部大数组导致栈溢出:main() 或函数内部定义的数组存在栈上,空间只有 1~2 MB。定义 int a[10000000] 这种大数组作为局部变量会直接崩溃,应改为全局变量(定义在所有函数之外)。
3
数组不能整体赋值或比较:a = ba == b 这类对数组变量整体操作的写法在 C++ 里都不合法(除非是刚定义时的初始化)。需要逐个元素用循环复制或比较。
4
插入删除的效率问题:数组的插入和删除都需要移动大量元素,最坏情况下要移动几乎整个数组,也就是说操作一次的耗时和数组长度成正比。如果需要频繁在中间位置插入删除,数组不是最优选择(后续会接触到更适合这种场景的数据结构)。