← 目录 / 第六章 · 数组 / 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() 外部(全局变量),它会被分配到静态存储区,空间充裕。

细心的话可能见过这样的写法——用一个变量(而不是像 5 这样写死的常量)来决定数组大小:

C++ · 用变量决定数组大小(变长数组 / VLA)
1int n;
2cin >> n;
3int arr[n] = {0}; // 数组大小是变量 n,而不是像 5 这样的常量
⚠️
这不是标准 C++ 语法,编译器之间表现不一致!C++ 标准要求数组大小必须是编译期就能确定的常量(比如字面量 5,或者 const int 常量),像上面这样用一个运行时才知道值的变量 n 做数组大小,叫做变长数组(VLA,Variable Length Array),标准 C++ 里其实是不允许的。

GCC / Clang(大多数 OJ 和信息学竞赛评测系统使用的编译器)把 VLA 作为编译器扩展支持,写了也能编译通过;但 MSVC(Visual Studio 自带编译器)不支持 VLA,同样的代码在 MSVC 下会直接编译报错。这和之前提到的 and/or__builtin_popcount 是同一类问题——都是"编译器扩展在不同编译器上表现不一致"。
💡
更安全的写法:如果需要"数组大小取决于输入",标准做法是用 vector(见后续 STL 章节)代替数组:vector<int> arr(n, 0);,效果一样能按变量 n 动态决定大小,而且是所有编译器都支持的标准写法,不用担心换个编译器就编译不过。竞赛中如果确实想用原生数组,更稳妥的办法是提前开一个足够大的固定大小数组(比如 int arr[100005];),根据题目给的数据范围上限来定,而不是直接用变量做数组大小。

访问数组中的元素

数组下标从 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
插入删除的效率问题:数组的插入和删除都需要移动大量元素,最坏情况下要移动几乎整个数组,也就是说操作一次的耗时和数组长度成正比。如果需要频繁在中间位置插入删除,数组不是最优选择(后续会接触到更适合这种场景的数据结构)。