跳转至

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';

程序的执行过程是:

  1. 先读入数据个数 n;
  2. 把累加器 sum 初始化为 0;
  3. 循环 n 次;
  4. 每次读入一个 x,再把 x 加入 sum;
  5. 循环结束后输出总和。

输入:

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 可以分成两部分理解:

  1. 先尝试读入 x;
  2. 读取成功并且 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 做了两件事:

  1. 尝试从输入中读入一个数据,保存到 x;
  2. 根据读取是否成功,给出一个可以用于判断的状态。

可以把它简单理解为:

读取成功 → 条件为真 → 执行循环体
读取失败 → 条件为假 → 退出循环

读取失败最常见的原因是已经到达 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)

可以按下面三个问题判断:

  1. 题目有没有直接告诉数据个数?
  2. 如果没有,题目有没有给出结束标记?
  3. 如果既没有个数,也没有结束标记,就读到 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. 循环读入题的检查顺序

写完程序后,可以按下面顺序检查:

  1. 是否先读入了 n,或者正确设置了哨兵条件?
  2. for 的循环次数是否正好等于数据个数?
  3. 是否每轮都成功读入了需要处理的数据?
  4. 累加器、累乘器或计数器是否只在循环外初始化一次?
  5. 哨兵值有没有被加入结果?
  6. while (cin >> x) 是否直接把读取写在条件中?
  7. 是否测试了最小数据、结束标记和文件结束等情况?

九、本课小结

循环读入的关键不是“把 cin 写进循环”这么简单,而是先判断题目规定的停止方式:

  • 指定个数:先读 n,用 for 循环恰好读 n 次;
  • 哨兵终止:用 while 读入,哨兵值只负责结束,不参与计算;
  • 读到 EOF:用 while (cin >> x),读取成功继续,读取失败停止;
  • 多变量读入:使用 while (cin >> a >> b),只有两个数据都读成功才处理。