Skip to content

第 16 章 一排柜子——一维数组


🏗️ 前情回顾:上一章你学会了用循环让计算机重复干活——同样的操作,它不嫌累。可你发现了吗?每次只能存一个数。"张三的成绩 95",存进 score。"李四的成绩 88"——完了,把张三的 95 盖掉了!全班 50 个成绩,难道要声明 50 个变量?score1, score2, score3, ..., score50,写到手抽筋,还怎么用循环处理?

💡 本章要解决的问题能不能把一列相同类型的数据"捆"在一起,用一个名字管,靠编号区分? 这就是数组——编程世界的"一排储物柜"。


🎯 本章目标

学完这一章,你能:

  • 理解数组是"同一类型数据的连续储物柜"
  • 声明数组、初始化数组、给数组元素赋值
  • 知道数组下标从 0 开始,末元素是 n-1
  • for 循环遍历数组的每个元素
  • 知道数组越界的危险并保证不越界
  • 用数组存储成绩、温度等批量数据

📖 故事引入

班主任的噩梦

开学第一周,班主任王老师拿到全班 50 个学生的期末成绩。她想把成绩输入电脑算平均分,于是她写:

cpp
int score1, score2, score3, score4, score5;
int score6, score7, score8, score9, score10;
// ……写到 score50 的时候,王老师已经不认识键盘了

更崩溃的是——算平均分:

cpp
double avg = (score1 + score2 + score3 + ... + score50) / 50.0;

这一行代码比清明上河图还长。而且如果隔壁班有 55 个人,所有代码都得重写。

王老师崩溃了。隔壁教信息学的李老师微微一笑:"你用数组啊——int scores[50],一行搞定。"

cpp
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 声明数组

声明数组要告诉计算机三件事:

  1. 柜子里装什么类型(intdoublechar……)
  2. 柜子的名字
  3. 有多少个柜子(用方括号 [] 指定数量)
cpp
int scores[50];         // 50 个整数柜子,装成绩
double temps[7];        // 7 个小数值子,装一周温度
char grades[30];        // 30 个字符柜子,装等级

声明时,方括号里的数字必须是常数(编译时就能确定的值)。不能是变量!

错误写法——先看到坑,再学怎么绕:

cpp
int n;
cin >> n;
int arr[n];  // ❌ 变量 n 不是常数!部分编译器可能通过,但不符合 C++ 标准

正确写法——先用题目给定的最大范围声明:

cpp
int 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 开始

cpp
int arr[5];              // 5 个柜子
arr[0] = 10;             // 第 1 个柜子放 10
arr[1] = 20;             // 第 2 个柜子放 20
arr[2] = 30;
arr[3] = 40;
arr[4] = 50;             // 第 5 个柜子(最后一个!)
下标01234
1020304050
第几个第 1 个第 2 个第 3 个第 4 个第 5 个

🧠 记住这个公式:一个有 n 个元素的数组,下标范围是 0n-1。不存在下标为 n 的元素!

常见踩坑——下标从 1 开始:

cpp
int arr[5];
for (int i = 1; i <= 5; i++) {
    cin >> arr[i];  // ❌ arr[5] 越界!只有 arr[0]~arr[4]
}

正确写法:

cpp
for (int i = 0; i < 5; i++) {
    cin >> arr[i];  // ✅ i 从 0 到 4,安全
}

16.4 初始化数组

你可以在声明的同时"装货"——这叫初始化

cpp
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。

常见踩坑——没初始化就读:

cpp
int arr[10];
cout << arr[3];  // ❌ 输出什么?不知道!可能是 0,也可能是奇怪的垃圾值

正确: 要么初始化 int arr[10] = {0};,要么先赋值再读。

16.5 用 for 循环遍历数组

数组的天然搭档是 for 循环。因为下标是连续整数(0, 1, 2, 3, …),正好用循环变量 i 依次访问:

cpp
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 开始。

常见遍历模式:

cpp
// 输入:逐个读入数组元素
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];
}

常见踩坑——循环条件写成 <=

cpp
int arr[5] = {10, 20, 30, 40, 50};
for (int i = 0; i <= 5; i++) {  // ❌ i 会变成 5,访问 arr[5] 越界!
    cout << arr[i] << " ";
}

正确:

cpp
for (int i = 0; i < 5; i++) {   // ✅ < 不是 <=,i 最多到 4
    cout << arr[i] << " ";
}

🧠 口诀:n 个元素,i 从 0 到 n-1,循环条件是 i < n

16.6 小综合:计算平均成绩

cpp
#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.2

16.7 数组越界——绝对不能碰的红线

假如你声明了 int arr[5];(下标范围 0~4),然后写了:

cpp
arr[5] = 100;   // ⚠ 越界!没有 5 号柜子!

这会发生什么?答案是——不知道

C++ 不会检查你是否越界。它可能:

  • 静悄悄地改掉了别的变量的值(最可怕!)
  • 程序崩溃
  • 看起来一切正常(但埋下了定时炸弹)

⚠️ 数组越界是 C++ 新手最容易犯的、后果最无法预测的错误。永远保证你的下标在 [0, n-1] 范围内。

一个安全的习惯:定义数组时多给几个位置:

cpp
int arr[105];  // 题目说最多 100 个,我声明 105 个,留缓冲

16.8 数组应用举例

场景 1:存储一周温度

cpp
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:反转数组

cpp
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. 分析题

以下代码输出什么?先推算,再上机验证:

cpp
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题。从声明→遍历→倒序→嵌套比较,数组基本操作全覆盖。。★核心(课堂必做) ◆拓展(课后练习)

级别题号链接覆盖知识点
★ 核心J0065https://hydro.ac/d/srqc/p/J0065数组声明、for遍历、嵌套比较
◆ 拓展J0066https://hydro.ac/d/srqc/p/J0066数组倒序遍历、while读入
◆ 拓展J0067https://hydro.ac/d/srqc/p/J0067数组存储过程值、while
◆ 拓展luogu-P1427https://hydro.ac/p/luogu-P1427数组倒序输出、不定量输入
◆ 拓展luogu-P1046https://hydro.ac/p/luogu-P1046数组、for+if、计数
◆ 拓展luogu-P1428https://hydro.ac/p/luogu-P1428数组、嵌套for、左边比较
◆ 拓展J0073https://hydro.ac/d/srqc/p/J0073数组、二维映射、显示

💡 练习建议:先完成 1 道★核心题,确保掌握本章基本方法;再完成 6 道◆拓展题,覆盖不同变式和细节。


配套练习

共7题。从声明→遍历→倒序→嵌套比较,数组基本操作全覆盖。★核心(课堂必做) · ◆拓展(课后练习)

级别题号链接覆盖知识点
★ 核心J0065https://hydro.ac/d/srqc/p/J0065数组声明、for遍历、嵌套比较
◆ 拓展J0066https://hydro.ac/d/srqc/p/J0066数组倒序遍历、while读入
◆ 拓展J0067https://hydro.ac/d/srqc/p/J0067数组存储过程值、while
◆ 拓展luogu-P1427https://hydro.ac/p/luogu-P1427数组倒序输出、不定量输入
◆ 拓展luogu-P1046https://hydro.ac/p/luogu-P1046数组、for+if、计数
◆ 拓展luogu-P1428https://hydro.ac/p/luogu-P1428数组、嵌套for、左边比较
◆ 拓展J0073https://hydro.ac/d/srqc/p/J0073数组、二维映射、显示

练习建议:先在课堂完成 1 道★核心题,掌握本章基本方法;课后完成 6 道◆拓展题,覆盖不同变式和细节。

自查清单

  • [ ] 我理解数组是"同类型连续存储"的储物柜
  • [ ] 我知道下标从 0 开始,末元素是 n-1
  • [ ] 我能声明数组并用 {0} 全部初始化
  • [ ] 我会用 for 循环遍历数组(i < n,不是 i <= n
  • [ ] 我知道数组越界是严重错误,会多留缓冲位置
  • [ ] 我理解为什么 int arr[n] 不合规(声明时大小要常量)
  • [ ] 我能用数组存储和批量处理数据(求和、求平均、找最值)

🚀 下章预告

数组帮你把成绩存下来了,50 个分数整整齐齐码在柜子里。但光存着有啥用?老师要统计"全班平均分多少"、"最高分是谁"、"90 分以上有几个人"、"有没有人考了满分"——这些都得在数组上"做操作"。

存是第一步,才是真本事。下一章,我们解锁数组的统计与变换魔法——求和、排序、找最值、桶计数、插入删除……把"储物柜"变成"加工厂"!