第 16 章 一排柜子——一维数组
🏗️ 前情回顾:上一章你学会了用循环让计算机重复干活——同样的操作,它不嫌累。可你发现了吗?每次只能存一个数。"张三的成绩 95",存进
score。"李四的成绩 88"——完了,把张三的 95 盖掉了!全班 50 个成绩,难道要声明 50 个变量?score1, score2, score3, ..., score50,写到手抽筋,还怎么用循环处理?💡 本章要解决的问题:能不能把一列相同类型的数据"捆"在一起,用一个名字管,靠编号区分? 这就是数组——编程世界的"一排储物柜"。
🎯 本章目标
学完这一章,你能:
- 理解数组是"同一类型数据的连续储物柜"
- 声明数组、初始化数组、给数组元素赋值
- 知道数组下标从 0 开始,末元素是
n-1 - 用
for循环遍历数组的每个元素 - 知道数组越界的危险并保证不越界
- 用数组存储成绩、温度等批量数据
📖 故事引入
班主任的噩梦
开学第一周,班主任王老师拿到全班 50 个学生的期末成绩。她想把成绩输入电脑算平均分,于是她写:
int score1, score2, score3, score4, score5;
int score6, score7, score8, score9, score10;
// ……写到 score50 的时候,王老师已经不认识键盘了更崩溃的是——算平均分:
double avg = (score1 + score2 + score3 + ... + score50) / 50.0;这一行代码比清明上河图还长。而且如果隔壁班有 55 个人,所有代码都得重写。
王老师崩溃了。隔壁教信息学的李老师微微一笑:"你用数组啊——int scores[50],一行搞定。"
int scores[50]; // 50 个柜子,整齐排好
for (int i = 0; i < 50; i++) {
cin >> scores[i]; // 循环输入,50 行变 3 行
}一排储物柜
小区楼下有一排储物柜,共 20 个,编号 0 到 19。每个柜子大小一样——只能放同样尺寸的包裹。
你去取快递。短信说"您的快递在 7 号柜"。你走到第 7 个柜子前,打开,拿到包裹。你不会去开 8 号柜——那不是你的。
这就是数组!数组 = 一排编号从 0 开始的储物柜,每个柜子装同一种类型的东西。
🧱 知识讲解
16.1 什么是数组
数组(array)是一组相同类型的变量,它们在内存里一个挨一个排好,用一个共同的名字来管理,用**编号(下标)**来区分。
🔑 三个关键词:相同类型、连续存储、下标访问。
类比:储物柜 = 数组,柜子编号 = 下标,柜子里的东西 = 数组元素。
16.2 声明数组
声明数组要告诉计算机三件事:
- 柜子里装什么类型(
int、double、char……) - 柜子的名字
- 有多少个柜子(用方括号
[]指定数量)
int scores[50]; // 50 个整数柜子,装成绩
double temps[7]; // 7 个小数值子,装一周温度
char grades[30]; // 30 个字符柜子,装等级声明时,方括号里的数字必须是常数(编译时就能确定的值)。不能是变量!
❌ 错误写法——先看到坑,再学怎么绕:
cppint n; cin >> n; int arr[n]; // ❌ 变量 n 不是常数!部分编译器可能通过,但不符合 C++ 标准✅ 正确写法——先用题目给定的最大范围声明:
cppint arr[1005]; // 题目说 n ≤ 1000,我声明 1005,留缓冲 int n; cin >> n; for (int i = 0; i < n; i++) cin >> arr[i];等到学了
vector(下册),你就可以用变量大小了。
16.3 下标——柜子的编号
数组用下标(index)来访问每一个元素。C++ 中下标从 0 开始。
int arr[5]; // 5 个柜子
arr[0] = 10; // 第 1 个柜子放 10
arr[1] = 20; // 第 2 个柜子放 20
arr[2] = 30;
arr[3] = 40;
arr[4] = 50; // 第 5 个柜子(最后一个!)| 下标 | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 值 | 10 | 20 | 30 | 40 | 50 |
| 第几个 | 第 1 个 | 第 2 个 | 第 3 个 | 第 4 个 | 第 5 个 |
🧠 记住这个公式:一个有 n 个元素的数组,下标范围是
0到n-1。不存在下标为 n 的元素!
❌ 常见踩坑——下标从 1 开始:
cppint arr[5]; for (int i = 1; i <= 5; i++) { cin >> arr[i]; // ❌ arr[5] 越界!只有 arr[0]~arr[4] }✅ 正确写法:
cppfor (int i = 0; i < 5; i++) { cin >> arr[i]; // ✅ i 从 0 到 4,安全 }
16.4 初始化数组
你可以在声明的同时"装货"——这叫初始化:
int arr[5] = {10, 20, 30, 40, 50}; // 5 个全装好
int evens[6] = {2, 4, 6}; // 前 3 个装好,后 3 个自动填 0
int zeros[100] = {0}; // 全部初始化为 0(常用技巧!)
int prices[] = {15, 25, 35, 45}; // 编译器自动数:4 个元素💡
int zeros[100] = {0};是竞赛里最常用的初始化方式——100 个柜子全部变成 0。
❌ 常见踩坑——没初始化就读:
cppint arr[10]; cout << arr[3]; // ❌ 输出什么?不知道!可能是 0,也可能是奇怪的垃圾值✅ 正确: 要么初始化
int arr[10] = {0};,要么先赋值再读。
16.5 用 for 循环遍历数组
数组的天然搭档是 for 循环。因为下标是连续整数(0, 1, 2, 3, …),正好用循环变量 i 依次访问:
int scores[5] = {95, 88, 76, 92, 80};
// 输出全部成绩
for (int i = 0; i < 5; i++) {
cout << "第" << i + 1 << "个同学:" << scores[i] << "分" << endl;
}输出:
第1个同学:95分
第2个同学:88分
第3个同学:76分
第4个同学:92分
第5个同学:80分🎯 循环变量
i从 0 到 4(不是 1 到 5!),因为数组下标从 0 开始。
常见遍历模式:
// 输入:逐个读入数组元素
for (int i = 0; i < n; i++) {
cin >> arr[i];
}
// 求和
int sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
}
// 找最大值
int maxVal = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] > maxVal) maxVal = arr[i];
}❌ 常见踩坑——循环条件写成
<=:cppint arr[5] = {10, 20, 30, 40, 50}; for (int i = 0; i <= 5; i++) { // ❌ i 会变成 5,访问 arr[5] 越界! cout << arr[i] << " "; }✅ 正确:
cppfor (int i = 0; i < 5; i++) { // ✅ < 不是 <=,i 最多到 4 cout << arr[i] << " "; }🧠 口诀:n 个元素,i 从 0 到 n-1,循环条件是
i < n。
16.6 小综合:计算平均成绩
#include <iostream>
using namespace std;
int main() {
int scores[5] = {95, 88, 76, 92, 80};
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += scores[i];
}
double avg = (double)sum / 5; // 强制转 double,保证小数不丢
cout << "总分:" << sum << endl;
cout << "平均分:" << avg << endl;
return 0;
}输出:
总分:431
平均分:86.216.7 数组越界——绝对不能碰的红线
假如你声明了 int arr[5];(下标范围 0~4),然后写了:
arr[5] = 100; // ⚠ 越界!没有 5 号柜子!这会发生什么?答案是——不知道。
C++ 不会检查你是否越界。它可能:
- 静悄悄地改掉了别的变量的值(最可怕!)
- 程序崩溃
- 看起来一切正常(但埋下了定时炸弹)
⚠️ 数组越界是 C++ 新手最容易犯的、后果最无法预测的错误。永远保证你的下标在
[0, n-1]范围内。
一个安全的习惯:定义数组时多给几个位置:
int arr[105]; // 题目说最多 100 个,我声明 105 个,留缓冲16.8 数组应用举例
场景 1:存储一周温度
double temps[7] = {23.5, 25.0, 22.8, 26.1, 24.3, 27.0, 25.6};
for (int i = 0; i < 7; i++) {
cout << "星期" << i + 1 << ":" << temps[i] << "℃" << endl;
}场景 2:反转数组
int arr[5] = {1, 2, 3, 4, 5};
// 前后交换:0↔4, 1↔3
for (int i = 0; i < 5 / 2; i++) {
int temp = arr[i];
arr[i] = arr[4 - i];
arr[4 - i] = temp;
}
// 输出:5 4 3 2 1✋ 动手试试
试试 1:声明一个长度为 10 的 int 数组,全部初始化为 0。用 for 循环依次赋值为 1~10,再输出整个数组。
试试 2:写一段代码故意访问 arr[10](下标越界),看看你的编译器有没有警告,程序跑起来发生了什么。记住:这是为了让你见识越界的危险,写正式代码时千万别这样做。
试试 3:输入 8 个同学的成绩(0~100),存入数组。然后输出:最高分、最低分、平均分。
试试 4:用数组和循环计算斐波那契数列(Fibonacci)的前 20 项。第 1 项和第 2 项是 1,之后每一项 = 前两项之和。输出全部 20 项。
🦶 你踩过这些坑吗?
- [ ] 坑 1:把下标当第几个——
arr[1]是第 1 个?错,它是第 2 个(下标从 0 开始) - [ ] 坑 2:循环条件写
i <= n——结果访问了不存在的arr[n],越界 - [ ] 坑 3:声明数组时用变量做大小——
int arr[n]不符合 C++ 标准 - [ ] 坑 4:数组没初始化就读——读出来是随机垃圾值
- [ ] 坑 5:以为
int arr[100] = {1}能把 100 个全初始化为 1——实际只有第一个是 1,其余是 0 - [ ] 坑 6:忘记"多留几个位置"——题目数据范围一变大,程序就崩
📝 练习
基础题
1. 选择题
(1)int arr[10]; 中,合法的下标范围是? A. 1~10 B. 1~9 C. 0~9 D. 0~10
(2)要声明一个能存 100 个 double 的数组,正确写法是? A. int arr[100]; B. double arr[100]; C. double arr(100); D. array double[100];
(3)访问 arr[5] 时,它实际是数组的第几个元素? A. 第 4 个 B. 第 5 个 C. 第 6 个 D. 不确定
(4)要将 100 个元素的数组全部初始化为 0,正确写法是? A. int arr[100] = {1}; B. int arr[100] = {0}; C. int arr[100] = 0; D. int arr[100] = {};
2. 填空题
(1)数组的三个关键特征:____ 类型、____ 存储、____ 访问。
(2)int arr[8]; 的最后一个有效下标是 ____。
(3)访问 arr[10] 但数组只有 10 个元素时,这种情况叫 ____。
(4)定义数组时多声明几个位置(如 int arr[105] 而非 int arr[100]),这是为了防止 ____。
提高题
3. 分析题
以下代码输出什么?先推算,再上机验证:
int arr[5] = {10, 20, 30};
for (int i = 0; i < 5; i++) {
cout << arr[i] << " ";
}4. 编程题
输入 10 个整数存入数组,然后倒序输出(从第 10 个到第 1 个)。
5. 编程题(分段统计)
输入 n(不超过 100)和 n 个 0~100 的成绩。统计并输出:
- 优秀(≥90)的人数
- 良好(75~89)的人数
- 及格(60~74)的人数
- 不及格(<60)的人数
挑战题
6. 编程题(去重基础)
输入 10 个整数存入数组,输出其中所有不重复的元素。(提示:对每个元素,检查它前面是否出现过相同的值。)
7. 编程题(相邻差异)
输入 n 个整数,存入数组后,计算并输出相邻两个元素之间的差值(后一个减前一个)。例如输入 5 8 3 9 2,输出 3 -5 6 -7。
🧠 本章小结
数组 = 一排同类型的储物柜
声明:
int arr[10]; // 10 个 int 柜子
double temps[7] = {23.5}; // 7 个 double,第一个 23.5
下标:
从 0 开始 → arr[0], arr[1], ..., arr[n-1]
遍历(黄金搭档):
for (int i = 0; i < n; i++) {
操作 arr[i];
}
初始化 = {0} → 全部清零(竞赛高频用法)
越界 → 访问 arr[n] 或更大 → 程序爆炸/静默错误 → 绝不!📝 配套练习
共7题。从声明→遍历→倒序→嵌套比较,数组基本操作全覆盖。。★核心(课堂必做) ◆拓展(课后练习)
| 级别 | 题号 | 链接 | 覆盖知识点 |
|---|---|---|---|
| ★ 核心 | J0065 | https://hydro.ac/d/srqc/p/J0065 | 数组声明、for遍历、嵌套比较 |
| ◆ 拓展 | J0066 | https://hydro.ac/d/srqc/p/J0066 | 数组倒序遍历、while读入 |
| ◆ 拓展 | J0067 | https://hydro.ac/d/srqc/p/J0067 | 数组存储过程值、while |
| ◆ 拓展 | luogu-P1427 | https://hydro.ac/p/luogu-P1427 | 数组倒序输出、不定量输入 |
| ◆ 拓展 | luogu-P1046 | https://hydro.ac/p/luogu-P1046 | 数组、for+if、计数 |
| ◆ 拓展 | luogu-P1428 | https://hydro.ac/p/luogu-P1428 | 数组、嵌套for、左边比较 |
| ◆ 拓展 | J0073 | https://hydro.ac/d/srqc/p/J0073 | 数组、二维映射、显示 |
💡 练习建议:先完成 1 道★核心题,确保掌握本章基本方法;再完成 6 道◆拓展题,覆盖不同变式和细节。
配套练习
共7题。从声明→遍历→倒序→嵌套比较,数组基本操作全覆盖。★核心(课堂必做) · ◆拓展(课后练习)
| 级别 | 题号 | 链接 | 覆盖知识点 |
|---|---|---|---|
| ★ 核心 | J0065 | https://hydro.ac/d/srqc/p/J0065 | 数组声明、for遍历、嵌套比较 |
| ◆ 拓展 | J0066 | https://hydro.ac/d/srqc/p/J0066 | 数组倒序遍历、while读入 |
| ◆ 拓展 | J0067 | https://hydro.ac/d/srqc/p/J0067 | 数组存储过程值、while |
| ◆ 拓展 | luogu-P1427 | https://hydro.ac/p/luogu-P1427 | 数组倒序输出、不定量输入 |
| ◆ 拓展 | luogu-P1046 | https://hydro.ac/p/luogu-P1046 | 数组、for+if、计数 |
| ◆ 拓展 | luogu-P1428 | https://hydro.ac/p/luogu-P1428 | 数组、嵌套for、左边比较 |
| ◆ 拓展 | J0073 | https://hydro.ac/d/srqc/p/J0073 | 数组、二维映射、显示 |
练习建议:先在课堂完成 1 道★核心题,掌握本章基本方法;课后完成 6 道◆拓展题,覆盖不同变式和细节。
自查清单:
- [ ] 我理解数组是"同类型连续存储"的储物柜
- [ ] 我知道下标从 0 开始,末元素是 n-1
- [ ] 我能声明数组并用
{0}全部初始化 - [ ] 我会用
for循环遍历数组(i < n,不是i <= n) - [ ] 我知道数组越界是严重错误,会多留缓冲位置
- [ ] 我理解为什么
int arr[n]不合规(声明时大小要常量) - [ ] 我能用数组存储和批量处理数据(求和、求平均、找最值)
🚀 下章预告
数组帮你把成绩存下来了,50 个分数整整齐齐码在柜子里。但光存着有啥用?老师要统计"全班平均分多少"、"最高分是谁"、"90 分以上有几个人"、"有没有人考了满分"——这些都得在数组上"做操作"。
存是第一步,算才是真本事。下一章,我们解锁数组的统计与变换魔法——求和、排序、找最值、桶计数、插入删除……把"储物柜"变成"加工厂"!