C1-07 循环读入
指定个数、哨兵值与文件结束
学习目标:理解为什么需要在循环中反复读入数据;能够根据题目中的输入格式选择
for、哨兵while或while (cin >> x);掌握指定个数、哨兵终止、读到 EOF 和多变量读入的基本写法;知道累加器、累乘器和计数器必须在循环外初始化,并能排查循环读入中的常见错误。
一、为什么需要循环读入
1. 之前的程序只读一组数据
前几课中,程序通常只读入一组数据,例如两个数 a 和 b:
int a, b;
cin >> a >> b;
cout << a + b << '\n';
这种写法适合“只处理一次”的问题。
但实际题目中,经常需要处理很多个数据:
- 读入
n个成绩,求总分; - 读入若干天的数量,统计总数;
- 不断读入正整数,直到读到
0; - 输入有多少个数并不确定,只能一直读到文件结束。
这时不能只写一次 cin >> x,而要把读入语句放进循环中,让程序重复完成“读入并处理”。
2. 循环读入的基本模式
循环读入通常可以分成两步:
读入一个数据 → 处理这个数据 → 再读下一个数据
例如求一批数的和:
sum = 0;
循环:
cin >> x;
sum += x;
本课的重点不是重新学习循环语法,而是判断循环应该读多少次、什么时候停止。
3. 三种常见场景
| 场景 | 数据个数 | 常用循环 | 典型说法 |
|---|---|---|---|
| 指定个数 | 已知 n 个 |
for |
“第一行是 n,接下来有 n 个数” |
| 哨兵终止 | 个数未知 | while |
“读到 0 为止” |
| 读到 EOF | 个数未知 | while (cin >> x) |
“输入若干个数,直到文件结束” |
二、三种循环读入场景总览
| 场景 | 标准写法 | 什么时候停止 | 结束标记是否参与计算 |
|---|---|---|---|
| 指定个数 | for (int i = 0; i < n; i++) |
读满 n 次 |
不存在额外结束标记 |
| 哨兵终止 | while (cin >> x && x != 0) |
读到哨兵值 | 不参与 |
| 读到 EOF | while (cin >> x) |
读取失败,通常是 EOF | 不存在哨兵值 |
可以先记住下面的选择口诀:
给了 n → 用 for
给了哨兵值 → 用 while + 哨兵判断
什么都没给 → 用 while (cin >> x)
三、指定个数读入:使用 for
1. 场景描述
题目如果明确告诉你有 n 个数据,就可以用 for 循环读 n 次。
例如:
第一行输入 n,接下来输入 n 个整数。
这里的 n 就是循环次数。
2. 标准写法
int n;
cin >> n;
int sum = 0;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
sum += x;
}
cout << sum << '\n';
程序的执行过程是:
- 先读入数据个数
n; - 把累加器
sum初始化为0; - 循环
n次; - 每次读入一个
x,再把x加入sum; - 循环结束后输出总和。
输入:
5
1 2 3 4 5
输出:
15
3. i < n 和 i <= n
读 n 个数据时,常见有两种写法:
// i 取 0, 1, ..., n-1,共 n 次
for (int i = 0; i < n; i++) {
cin >> x;
}
// i 取 1, 2, ..., n,共 n 次
for (int i = 1; i <= n; i++) {
cin >> x;
}
两种写法都正确,但起点和条件必须配套。
| 初始化 | 条件 | 循环次数 | 说明 |
|---|---|---|---|
i = 0 |
i < n |
n 次 |
常用于读入 n 个数据 |
i = 1 |
i <= n |
n 次 |
更接近“第 1 次到第 n 次” |
i = 0 |
i <= n |
n + 1 次 |
错误,多读一次 |
关键提醒: 从
0开始通常配< n;从1开始通常配<= n。不要只看条件,还要同时检查初始化值。
4. 多个数据分别统计
如果每次读入多个数据,可以设置多个累加器。
例如,统计 n 天的金牌、银牌和铜牌总数:
int n;
cin >> n;
int gold = 0;
int silver = 0;
int bronze = 0;
for (int i = 0; i < n; i++) {
int g, s, b;
cin >> g >> s >> b;
gold += g;
silver += s;
bronze += b;
}
cout << gold << ' ' << silver << ' ' << bronze << '\n';
这里每轮读入三个数,但循环次数仍然由 n 决定。三个累加器都在循环外初始化,循环内只负责累加。
四、哨兵终止读入:使用 while
1. 什么是哨兵值
有些题目不会告诉你数据有多少个,而是规定读到某个特殊值就停止。例如:
读入若干个正整数,以 0 结束,0 不参与计算。
这里的 0 就是哨兵值。它的作用像站岗的标记:程序读到它,就知道输入结束。
常见的哨兵值有:
- 正整数序列以
0结束; - 非负数序列以
-1结束; - 题目明确指定的其他特殊值。
2. 推荐写法:在条件中排除哨兵
int sum = 0;
int x;
while (cin >> x && x != 0) {
sum += x;
}
cout << sum << '\n';
输入:
3 5 7 0
输出:
15
条件 cin >> x && x != 0 可以分成两部分理解:
- 先尝试读入
x; - 读取成功并且
x不是0时,才执行循环体。
因此,0 只负责结束循环,不会被加入 sum。
3. 使用 break 的写法
也可以先读入,再用 break 跳出循环:
int sum = 0;
int x;
while (cin >> x) {
if (x == 0) {
break;
}
sum += x;
}
cout << sum << '\n';
break 表示立即结束当前循环。这个写法和前面的写法等价,逻辑分成了“读入”“判断”“处理”三步,初学时比较直观。
4. 哨兵值不能参与计算
哨兵是结束标记,不是普通数据。特别是在求乘积或计数时,把哨兵混入结果会产生明显错误。
例如,读到 -1 为止,求前面所有数的乘积:
int product = 1;
int x;
while (cin >> x && x != -1) {
product *= x;
}
cout << product << '\n';
输入:
2 3 4 -1
输出:
24
如果把 -1 也乘进去,结果就会变成 -24,说明哨兵值没有被正确排除。
五、读到 EOF:while (cin >> x)
1. 什么是 EOF
EOF 是 End Of File 的缩写,意思是“文件结束”。
在在线评测系统中,程序的输入通常来自一个文件。文件中的数据全部读完后,就到达 EOF。此时没有下一个数据可以读入,循环就应该结束。
有些题目会说:
输入若干个整数,求它们的和。
如果题目没有给出 n,也没有给出 0、-1 之类的结束标记,就可以读到 EOF。
2. 标准写法
int sum = 0;
int x;
while (cin >> x) {
sum += x;
}
cout << sum << '\n';
输入:
1 2 3 4 5
输出:
15
这里没有写固定次数,也没有判断某个特殊数。程序会不断读入,直到读取失败。
3. while (cin >> x) 为什么能控制循环
cin >> x 做了两件事:
- 尝试从输入中读入一个数据,保存到
x; - 根据读取是否成功,给出一个可以用于判断的状态。
可以把它简单理解为:
读取成功 → 条件为真 → 执行循环体
读取失败 → 条件为假 → 退出循环
读取失败最常见的原因是已经到达 EOF。也可能是输入内容和变量类型不匹配,例如 x 是整数,却读到了不能解释为整数的内容。
初学阶段只要记住:
while (cin >> x)的意思是“读入成功就继续,读不到就停止”。
4. 本地键盘调试
在 OJ 中,文件读完会自动产生 EOF;但在本地用键盘输入时,程序不知道你是否还要继续输入,所以可能一直等待。
输入完数据后,可以用快捷键告诉程序“输入结束”:
| 系统 | 快捷键 | 说明 |
|---|---|---|
| Windows | Ctrl + Z,再按回车 |
通常先提交当前行,再结束输入 |
| Linux / macOS | Ctrl + D |
在新行开头结束输入 |
这只是本地键盘调试的方法,在线评测时不需要手动操作。
5. 不要用 while (!cin.eof())
初学者有时会写:
while (!cin.eof()) {
cin >> x;
sum += x;
}
这种写法容易把最后一次读取失败时的旧数据再次处理,产生错误结果。
正确写法是把读取动作直接放进条件:
while (cin >> x) {
sum += x;
}
六、多变量循环读入
1. while (cin >> a >> b)
如果每次需要读入两个数,可以把两个读入动作连续写在条件中:
int a, b;
while (cin >> a >> b) {
cout << a + b << '\n';
}
它表示:
只有 a 和 b 都读取成功,才执行一次循环体。
输入:
1 2
3 4
5 6
输出:
3
7
11
每读入一对 a、b,就立即计算并输出它们的和。
2. 不要写成逗号表达式
错误写法:
while (cin >> a, b) {
// ...
}
这里的逗号不是“继续读入 b”,而是逗号表达式的一部分,b 并没有通过 cin 读入。
正确写法:
while (cin >> a >> b) {
// ...
}
3. 指定次数和读到 EOF 的区别
如果题目说“第一行有 n,接下来有 n 行”,使用 for:
int n;
cin >> n;
for (int i = 0; i < n; i++) {
int a, b;
cin >> a >> b;
cout << a + b << '\n';
}
如果题目没有给出 n,只说“输入若干行,每行两个数”,使用:
int a, b;
while (cin >> a >> b) {
cout << a + b << '\n';
}
两段代码都能处理多对数据,但停止条件不同:
for根据给出的n停止;while (cin >> a >> b)根据读取失败停止。
七、三种场景怎样选择
| 题目中的关键词 | 选择的循环 | 典型代码 |
|---|---|---|
“第一行是 n”“接下来有 n 个” |
for |
for (int i = 0; i < n; i++) |
“读到 0 为止”“以 -1 结束” |
while |
while (cin >> x && x != 0) |
| “输入若干个数”“直到文件结束” | while |
while (cin >> x) |
| “每行两个数,输入若干行” | while |
while (cin >> a >> b) |
可以按下面三个问题判断:
- 题目有没有直接告诉数据个数?
- 如果没有,题目有没有给出结束标记?
- 如果既没有个数,也没有结束标记,就读到 EOF。
选择口诀: 给个数用
for,给哨兵用while加判断,什么都不给用while (cin >> x)。
八、常见错误与注意事项
1. 有编译器提示的错误
| 编号 | 错误 | 常见识别信息 | 后果 | 改正 |
|---|---|---|---|---|
| E1 | for 或 while 的括号没有配对 |
expected ')' |
循环结构无法编译 | 检查圆括号和花括号 |
| E2 | 把 while (cin >> x) 写成不完整的表达式 |
expected primary-expression 等 |
条件无法判断 | 检查 cin、>> 和变量名 |
| E3 | 使用了没有定义的变量 | 'x' was not declared |
编译器找不到读入目标 | 先定义 int x 等变量 |
| E4 | 多变量读入符号写错 | 语法错误或逻辑异常 | a、b 没有按预期读入 |
写成 cin >> a >> b |
2. 没有固定报错,但结果或运行过程不对
| 编号 | 错误 | 后果 | 改正 |
|---|---|---|---|
| E5 | 累加器在循环体内初始化 | 每轮清零,结果只保留最后一次 | sum = 0 放在循环外 |
| E6 | 忘记在循环体内读入数据 | 变量不变,可能死循环 | 每轮先执行 cin >> x,或把它写在 while 条件中 |
| E7 | i = 0 却写成 i <= n |
多读一次,读入次数变成 n+1 |
使用 i < n |
| E8 | 哨兵值参与计算 | 求和、乘积或计数结果错误 | 在条件中排除哨兵,或读到后先 break |
| E9 | 把 while (cin >> x) 写成 while (!cin.eof()) |
最后一次读取失败时可能重复处理旧数据 | 直接把读取写进条件 |
| E10 | 把哨兵判断中的 != 写成 = |
赋值代替判断,循环行为异常 | 判断使用 ==、!= |
| E11 | 只测试了普通数据 | 边界数据或空输入时结果错误 | 测试 n=1、只有哨兵、多个数据和 EOF |
3. 累加器、累乘器和计数器的位置
这些变量通常都要在循环外初始化一次:
int sum = 0;
int count = 0;
long long product = 1;
while (cin >> x && x != 0) {
sum += x;
count++;
product *= x;
}
不要把初始化写在循环体内:
// 错误示例
while (cin >> x) {
int sum = 0;
sum += x;
}
每次进入循环,sum 都会重新变成 0,之前的结果就丢失了。
4. 循环读入题的检查顺序
写完程序后,可以按下面顺序检查:
- 是否先读入了
n,或者正确设置了哨兵条件? for的循环次数是否正好等于数据个数?- 是否每轮都成功读入了需要处理的数据?
- 累加器、累乘器或计数器是否只在循环外初始化一次?
- 哨兵值有没有被加入结果?
while (cin >> x)是否直接把读取写在条件中?- 是否测试了最小数据、结束标记和文件结束等情况?
九、本课小结
循环读入的关键不是“把 cin 写进循环”这么简单,而是先判断题目规定的停止方式:
- 指定个数:先读
n,用for循环恰好读n次; - 哨兵终止:用
while读入,哨兵值只负责结束,不参与计算; - 读到 EOF:用
while (cin >> x),读取成功继续,读取失败停止; - 多变量读入:使用
while (cin >> a >> b),只有两个数据都读成功才处理。