← 课程目录
第 14 讲 模块五:批量处理数据

数组

14 / 25
💡
本节课目标
从这一讲开始进入模块五:批量处理数据。今天学数组—— 用一个名字集中管理一批同类型的数据,用下标(从 0 开始)读写其中每一个元素。
开场

5 门课的成绩,5 个变量?

📖 理论讲解

一个变量管一个数,很快就管不过来

如果要存 5 门课的成绩,按前面学的办法,只能声明 5 个变量:

many_vars.cpp CPP
#include <iostream>

int main() {
    int score1, score2, score3, score4, score5;
    std::cin >> score1 >> score2 >> score3 >> score4 >> score5;

    int sum = score1 + score2 + score3 + score4 + score5;
    std::cout << "总分:" << sum << std::endl;
    return 0;
}
⚠️
变量一多,麻烦就来了
5 门课还能勉强应付,如果是全班 30 个人的成绩呢?声明 30 个变量、 再手动把 30 个变量加起来求总分——而且这些变量各自独立,没法用循环去遍历它们
📖 理论讲解

数组:把同类型的数据装进一排格子

数组就是把一批同类型的数据,用一个名字 集中存起来,像一排连续编号的储物格。每个格子用下标区分, 借助下标,就能配合循环批量处理——这正是下一讲要做的事。

第一段

声明数组

📖 理论讲解

语法:类型 数组名[长度];

array_declare.cpp CPP
int scores[5];   // 声明一个能装 5 个 int 的数组,起名叫 scores
部分例子含义
类型int数组里每个元素的类型,必须都一样
数组名scores整个数组的名字
长度[5]这个数组一共有 5 个格子
⚠️
声明了不代表有值
int scores[5]; 只是划出了 5 个格子,没有自动填 0 或任何默认值—— 此时每个元素都是未初始化的,不能在赋值前读取:
uninit_demo.cpp CPP
int scores[5];   // 声明了,但没有赋值
std::cout << scores[0] << std::endl;   // 错误示例:读取未初始化的 int
🚫
这不是“随机数”,而是未定义行为
在 C++ 中读取未初始化的 int 属于未定义行为: 标准不保证程序会打印什么,甚至不保证程序还能按你写的顺序运行。 所以用数组前,一定要先用初始化列表赋值,或者逐个用 cin / 赋值填好, 不能把它理解成“只是得到一个不可预测的旧数字”。
初始化列表给少了,会自动补 0
和“完全不初始化”不同,只要用了花括号,哪怕只给了一部分值, 剩下的格子也会规规矩矩地补成 0,不会是垃圾值:
padded_init.cpp CPP
int scores[5] = {90, 85};   // 只给了 2 个值
// scores[2]、scores[3]、scores[4] 会自动补成 0
Python

Python:列表,长度可以随时变

scores = [90, 85, 70, 60, 95]

scores.append(100) # 随时加

VS
C++

C++:数组,长度声明时就定死

int scores[5] = {90, 85, 70, 60, 95};

不能再往里“加”一个(后面会学 vector

第二段

下标:从 0 开始数

📖 理论讲解

第一个元素的下标是 0,不是 1

声明的同时可以直接给出全部初始值,用花括号 { } 包住,逗号隔开。 访问某个元素,用数组名 + 中括号 + 下标——但下标是从 0 开始的,长度为 5 的数组,最后一个元素的下标是 4,不是 5

array_init.cpp CPP
#include <iostream>

int main() {
    int scores[5] = {90, 85, 70, 60, 95};

    std::cout << "第一个:" << scores[0] << std::endl;
    std::cout << "第三个:" << scores[2] << std::endl;
    std::cout << "最后一个:" << scores[4] << std::endl;
    return 0;
}
⚠️
数组越界:C++ 不会帮你检查
scores[5] 对一个长度为 5 的数组来说,根本不存在这个格子:
out_of_bounds.cpp CPP
int scores[5] = {90, 85, 70, 60, 95};
std::cout << scores[5] << std::endl;   // 危险!根本不存在下标 5
🚫
越界访问也是未定义行为
很多语言(比如 Python)越界会直接报错、程序停下来;C++ 不会—— 使用原生数组的下标访问时没有自动范围检查。越界读写属于未定义行为, 可能碰巧没有明显现象,也可能破坏别的数据或直接崩溃。 所以用下标前,一定要想清楚:它在 0 到“长度减 1”这个范围内吗?
⚡ 动手实操 约 5 分钟

数一数下标

array_init.cpp 跑起来,再自己添加一行,打印第二个元素 (下标是几?)。再判断 scores[-1]scores[4]scores[5] 中哪些下标合法,并说明原因。不要通过“运行一下没崩溃” 来判断越界代码是否正确——未定义行为即使暂时没有明显现象,也仍然是错误。

拓展:让工具帮你抓越界
在真实项目里,可以在编译时开启 AddressSanitizer 等检查工具来发现许多越界问题。 例如 Clang / GCC 常用参数是 -fsanitize=address,undefined -g。工具能帮忙定位错误, 但不能代替写出正确的下标范围。
第三段

读写数组元素

📖 理论讲解

cin 和赋值都能对准某个下标操作

std::cin >> scores[0]; 把输入的值存进下标 0 的格子; scores[1] = scores[1] + 5; 则是先取出下标 1 的值、加上 5、 再存回同一个格子——和普通变量的读写规则完全一样,只是多了一层“选中哪个格子”的下标。

read_write_array.cpp CPP
#include <iostream>

int main() {
    int scores[3];

    std::cout << "请输入 3 门课的成绩:" << std::endl;
    std::cin >> scores[0];
    std::cin >> scores[1];
    std::cin >> scores[2];

    scores[1] = scores[1] + 5;   // 第二门课加 5 分(比如老师给了补偿分)

    std::cout << "调整后:" << std::endl;
    std::cout << scores[0] << " " << scores[1] << " " << scores[2] << std::endl;
    return 0;
}
⚡ 动手实操 约 8 分钟

改一改这个数组

read_write_array.cpp 基础上,把数组长度从 3 改成 5, 对应地把 cin 的读入语句也补齐到 5 个,运行并验证。

第四段

数组的长度,声明后就定死了

📖 理论讲解

想改长度?做不到

int scores[5] 一旦声明,这个数组就永远是 5 个格子, 没有办法在中途“再多装一个”。如果运行时才知道要存多少数据(比如“这次考试有几个人报名不确定”), 数组就不太好用了——下节课先学会怎么用循环搭配数组批量处理数据, 模块五后面还会学一种长度可以随时变的容器:vector

实战

动手写三个数组

练习 14-1

打印指定位置

声明一个 int scores[4],用初始化列表直接赋值 {88, 92, 76, 100},分别打印下标 1 和下标 3 的元素。

练习 14-2

三个价格求和

声明 int prices[3],依次用 cin 读入 3 个价格, 不借助循环,直接把 prices[0] + prices[1] + prices[2] 算出来并打印。

练习 14-3

修改某个元素

声明一个 int scores[5] 并用初始化列表赋值,先完整打印一遍, 然后把下标 2 的元素改成 100,再完整打印一遍, 对比两次输出的差异。

收尾

小结

📖 理论讲解

这节课你掌握了

能力用到的工具
集中声明一批同类型数据类型 数组名[长度];
声明时直接赋初值{ 值1, 值2, … }
读写某个元素数组名[下标],下标从 0 开始
避免踩坑下标不能超过“长度减 1”,否则越界
💡
下节课预告
今天的例子都是手动写死下标 012…… 如果数组有 30 个元素,难道要写 30 行吗?下节课把循环和数组结合起来, 用循环变量当下标,批量遍历、求和、找最大值最小值。