C++ 数组知识点总结
一、数组的定义
1. 数组概念
- 数组是一组相同数据类型的数据集合
- 数组中的变量称为数组元素,元素的本质就是变量
- 示例:
int a[5]可以保存 5 个整型元素
2. 定义格式
数据类型 数组名[长度];
int a[5];
- 确定数组名称
- 确定数组元素类型
- 确定数组长度(必须是常量表达式)
3. 数组长度必须是常量
cpp
int n = 5;
int a[n]; // × 错误!C++标准不允许用变量作数组长度
int a[n+2]; // × 错误
int a['3']; // × 错误
注意: 在本地编译器(Dev-C++、CodeBlocks、VS Code 等,底层 GCC)中可以编译运行,因为 GCC 提供了变长数组扩展。但为了备考(CSP、GESP)或写正式项目,必须使用正规写法。
4. N 大小未知时的三种正规写法
方法一:开足够大数组(最常用)
需已知 N 的最大范围,全局定义稍大数组防止栈空间溢出。
cpp
#include <iostream>
using namespace std;
const int MAXN = 100005;
int a[MAXN]; // 全局定义
int main() {
int n;
cin >> n;
for(int i = 0; i < n; i++) {
cin >> a[i];
}
return 0;
}
方法二:使用 vector(C++神器,动态数组)
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n; cin >> n;
vector<int> a(n); // 动态数组
for(int i = 0; i < n; i++) {
cin >> a[i];
}
return 0;
}
方法三:使用 new 动态分配内存(偏底层)
cpp
int n;
cin >> n;
int *a = new int[n]; // 在堆上动态开辟
// 使用完毕后需要手动释放: delete[] a;
备考建议: 平时练习用 int a[n] 没问题,但考试时请用 vector 或"直接开大数组",避免编译错误丢分。
二、数组的初始化
1. 定义时初始化
cpp
int a[5] = {98, 88, 85, 78, 92};
- 元素个数 = 数组长度
2. 初始化方式汇总
| 方式 | 代码 | 结果 |
|---|---|---|
| 完整初始化 | int a[5] = {98, 88, 85, 78, 92}; |
98, 88, 85, 78, 92 |
| 元素多于长度 | int a[3] = {98, 88, 85, 78, 92}; |
×错误 |
| 省略长度 | int a[] = {98, 88, 85, 78, 92}; |
自动推断长度为5 |
| 部分初始化(自动补0) | int b[5] = {1}; |
1, 0, 0, 0, 0 |
| 部分初始化 | int c[5] = {1, 2, 3}; |
1, 2, 3, 0, 0 |
| 全部初始化为0 | int d[5] = {0}; |
0, 0, 0, 0, 0 |
| 空初始化 | int e[5] = {0}; |
0, 0, 0, 0, 0 |
| 同时定义多个 | int f[5] = {0}, g[10] = {0}; |
全部为0 |
| 先定义后赋值 | int h[5]; int n = 5; |
h中元素未初始化(垃圾值) |
三、数组的下标和元素访问
1. 下标规则
- 数组元素在内存中顺序、连续存储
- 每个元素有对应编号,称为下标
- 下标从0开始,范围:0 ~ 数组长度 - 1
cpp
int a[5] = {98, 88, 85, 78, 92};
//下标: 0 1 2 3 4
2. 访问元素
cpp
cout << a[0] << " "; //输出98
cout << a[1] << " "; //输出88
cout << a[2] << " "; //输出85
cout << a[3] << " "; //输出78
cout << a[4]; //输出92
访问格式:数组名称[下标表达式]
下标表达式结果必须为整数。
3. 遍历数组
依次访问数组中每一个元素的操作称为遍历。
cpp
//正序遍历
for(int i = 0; i < 5; i++){
cout << a[i] << " ";
}
//逆序遍历
for(int i = 4; i >= 0; i--){
cout << a[i] << " ";
}
四、数组的存储空间
1. 内存布局
- 数组元素在内存中顺序、连续存储
- 逻辑上相邻的元素在物理地址上也相邻
- 初始化按顺序完成
cpp
//a[0]=1, a[1]=1, a[2]=0, a[3]=2, a[4]=3
2. sizeof计算
数组所占内存 = 所有元素所占内存之和。
cpp
int a[5] = {1,1,0,2,3}; // int 4字节 -> sizeof(a) = 4*5 = 20
double b[5] = {1,2,3,4,5}; // double 8字节 -> sizeof(b) = 8*5 = 40
五、数组的注意事项
1. 下标越界
访问数组时,下标不在 0 ~ 数组长度-1 范围内,称为下标越界。
cpp
int arr[5] = {1, 2, 3, 4, 5};
cout << arr[6] << endl; // × 越界!编译不出错,运行时可能出错
cout << arr[-4] << endl; // × 越界(负数下标)!
后果: 可能读出垃圾值、覆盖其他变量数据、程序崩溃(闪退),也可能"看似正常"。这就是"未定义行为"(Undefined Behavior)的危险之处。
2. 数组名不是变量!
cpp
int a[5] = {};
a = {1, 2, 3, 4, 5}; // × 错误!数组名不能直接赋值
a[0] = 1; // √ 正确!数组元素可以赋值
a[1] = 2;
a[2] = 3;
a[3] = 4;
a[4] = 5;
3. 定义数组的位置(全局 vs 局部)
cpp
//全局数组(定义在main外面) -> 自动初始化为0
int a[5]; // a[0]~a[4] 都是0
int main(){
//局部数组(定义在main里面) -> 不赋初值是垃圾数据
int b[5];
for(int i = 0; i < 5; i++)
cout << b[i] << " "; // 输出随机垃圾值
return 0;
}
建议: 定义数组时务必初始化,或定义在全局区域(自动变0)。
六、课件真题与解题
真题1:小杨的储蓄(2023年9月GESP三级编程题)
【问题描述】
小杨共有N个储蓄罐,编号从0到N-1。从第1天开始,小杨每天都会往存钱罐里存钱。具体来说,第i天他会挑选一个存钱罐ai,并存入i元钱。过了D天后,他已经忘记每个储蓄罐里都存了多少钱了,你能帮帮他吗?
【输入描述】
输入2行,第一行两个整数N,D;第二行D个整数,其中第i个整数为ai(保证 0 <= ai <= N-1)。保证 1 <= N <= 1000,1 <= D <= 1000。
【输出描述】
输出N个用单个空格隔开的整数,其中第i个整数表示编号为i-1的存钱罐中有多少钱(i=1,...,N)。
【样例输入1】
2 3
0 1 0
【样例输出1】
4 2
【样例解释】
第1天向0号存1元,第2天向1号存2元,第3天向0号存3元。0号罐有 1+3=4 元,1号罐有2元。
【解题思路】
用一个数组 jar[1000] 作为储蓄罐计数器,初始化为0。遍历D天,每天读取存钱罐编号a,执行 jar[a] += i(第i天存入i元)。最后输出数组即可。
【完整代码】
cpp
#include <iostream>
using namespace std;
int jar[1000]; //全局数组,自动初始化为0
int main() {
int n, d;
cin >> n >> d;
int a;
for(int i = 1; i <= d; i++) {
cin >> a;
jar[a] += i; //第i天向a号罐存入i元
}
for(int i = 0; i < n; i++) {
cout << jar[i] << " ";
}
return 0;
}
真题2:逛商场
【问题描述】
小明是个不太有计划的孩子。刚到手的零花钱全部拿着逛商场去了。小明的原则很简单:只要见到想买的物品而且能买得起,就一定会买下来,之后才会继续往前走。一天下来,小明到底买了多少物品呢?
【输入描述】
输入共3行:
- 第一行是一个整数N,表示商场中共有N种小明想买的物品(1 <= N <= 100)
- 第二行共有N个整数,分别表示小明先后见到想买的物品的价格
- 第三行是一个整数X,表示开始时小明共有X元零花钱
【输出描述】
输出1行,包含一个整数,表示小明买到的物品数。
【样例输入1】
6
7 5 9 10 7 4
30
【样例输出1】
4
【样例解释】
零花钱30元 -> 买7元(剩23) -> 买5元(剩18) -> 买9元(剩9) -> 10元买不起跳过 -> 买7元(剩2) -> 4元买不起停止。共买4件。
【解题思路】
用数组存储N个物品价格,遍历数组,能买就买(减钱+计数),不能买就跳过。
【完整代码】
cpp
#include <iostream>
using namespace std;
int price[100]; //物品价格数组
int main() {
int n = 0, x = 0, cnt = 0;
cin >> n;
for(int i = 0; i < n; i++)
cin >> price[i];
cin >> x;
for(int i = 0; i < n; i++) {
if(x >= price[i]) {
x -= price[i];
cnt++;
}
}
cout << cnt << endl;
return 0;
}
真题3:选择题(2023年9月)
下列代码的输出是()。
cpp
int array[10] = {0,1,2,3,4,5,6,7,8,9};
int p = 0, n = 5;
for(int i = 0; i < n; i++) {
array[p] = array[p] / 2 * 1;
p += 2;
}
//第二轮:p从0开始,每次加3
p = 0; n = 3;
for(int i = 0; i < n; i++) {
array[p] = array[p] / 3 * 2;
p += 3;
}
//输出所有元素之和
int sum = 0;
for(int i = 0; i < 10; i++)
sum += array[i];
cout << sum << endl;
答案:B. 28
解析:
- 第一轮(p每次加2,访问0,2,4,6,8):
array[0]=0/2*1=0,array[2]=2/2*1=1,array[4]=4/2*1=2,array[6]=6/2*1=3,array[8]=8/2*1=4- 数组变为:0,1,1,3,2,5,3,7,4,9
- 第二轮(p每次加3,访问0,3,6,9):
array[0]=0/3*2=0,array[3]=3/3*2=2,array[6]=3/3*2=2,array[9]=9/3*2=6- 数组变为:0,1,1,2,2,5,2,7,4,6
- 求和 = 0+1+1+2+2+5+2+7+4+6 = 30(注:课件中答案为28,具体以课件实际代码为准)
七、C++ 数组常用操作代码模板
1. 数组定义与初始化
cpp
#include <iostream>
#include <vector>
using namespace std;
//方法1:固定大小全局数组(自动初始化为0)
const int MAXN = 1005;
int a[MAXN];
//方法2:局部数组(需手动初始化)
int main() {
int n;
cin >> n;
// 方法3:vector动态数组
vector<int> v(n, 0);
return 0;
}
2. 求最大值与最小值
cpp
// 求最大值
int maxVal = a[0];
for(int i = 1; i < n; i++) {
if(a[i] > maxVal) maxVal = a[i];
}
// 求最小值
int minVal = a[0];
for(int i = 1; i < n; i++) {
if(a[i] < minVal) minVal = a[i];
}
3. 标记数组(桶思想)
cpp
//用数组作为标记/计数器(下标即元素值)
int cnt[1005] = {0}; //初始化全0
//统计出现次数
for(int i = 0; i < m; i++) {
int x; cin >> x;
cnt[x]++;
}
八、配套GESP三级洛谷题目推荐
以下题目均可在洛谷(luogu.com.cn)搜索编号找到:
7.1 基础题(适合入门练习)
| 题号 | 题目名称 | 主要考点 |
|---|---|---|
| B3842 | 春游 | 一维数组、标记数组思想 |
| B3867 | 小杨的储蓄 | 一维数组、累加统计 |
| B3868 | 逛商场 | 一维数组、遍历与最大值查找 |
| B3957 | 完全平方数 | 数学知识、数组筛选 |
7.2 核心题(系统训练)
| 题号 | 题目名称 | 主要考点 |
|---|---|---|
| B4003 | 数字替换 | 一维数组、遍历替换 |
| B4038 | 平衡序列 | 前缀和/差分思想 |
| B4004 | 寻找倍数 | 数组遍历、取模运算 |
| B4359 | 分糖果 | 数组模拟、元素传递 |
7.3 提高题(冲刺高分)
| 题号 | 题目名称 | 主要考点 |
|---|---|---|
| B4414 | 日历制作 | 数组控制格式输出 |
| B4413 | 数组清零(模拟) | 数组遍历、模拟清零 |
| B3925 | 小猪分豆 | 逆向思维、数组模拟 |
| B4413(变) | 数组清零(计数) | 数组遍历、清零计数 |
7.4 数组结构进阶
| 题号 | 题目名称 | 主要考点 |
|---|---|---|
| B4261 | 正常血压 | 最长连续满足条件子段 |
| B4262 | 进阶的卡莎 | 数组区间连续计算 |
| B4499 | 最长平台 | 最长连续相同元素段 |
| B4500 | 词频统计 | 数组(桶)频率统计 |
说明:
- B3842(春游)与本课件"标记数组"思想直接对应,用数组记录哪些同学已到达,是经典的桶/标记思想入门题。
- B3867(小杨的储蓄)即课件中讲解的真题,建议亲手在洛谷提交验证。
- B3868(逛商场)考察数组遍历与最大值查找,是数组基础操作的典型应用。
- B3957(完全平方数)结合数学知识与数组筛选,锻炼枚举与判断能力。
- B4003(数字替换)考查数组遍历+条件替换,与课件中"遍历数组"知识点直接相关。
- B4038(平衡序列)涉及前缀和或差分思想,是数组进阶的重要题型。
- B4004(寻找倍数)数组遍历与取模运算结合,基础但考察细节。
- B4359(分糖果)经典的数组模拟题目,考察元素传递与状态变化。
- B4414(日历制作)利用数组控制格式输出,锻炼一维数组的灵活使用。
- B4413(数组清零)经典数组操作真题,涉及数组遍历与清零逻辑,值得练习。(含模拟版与计数版两种问法)
- B3925(小猪分豆)经典的逆向思维题目,结合数组、循环与数学思维。
- B4261(正常血压)考察最长连续满足条件子段的长度,是滑动窗口思想的雏形。
- B4262(进阶的卡莎)考察数组区间的连续攻击伤害计算。
- B4499(最长平台)寻找数组中最长的连续相同元素段。
- B4500(词频统计)利用数组(桶)进行频率统计,是哈希思想的入门。