C1-09 循环与分支
循环中的条件判断、continue 与 break
学习目标:理解为什么要把循环和
if组合使用;能够在循环中进行筛选、分类、条件求和和条件计数;掌握continue“跳过本次循环”和break“结束整个循环”的区别;能够使用循环与分支完成求最值、查找第一个满足条件的数等基本问题;知道如何排查continue导致的死循环、最值初值错误和break、continue混用等问题。
一、为什么需要循环与分支
1. 循环和分支分别解决什么问题
前面的课程分别学习了循环和分支:
- 循环让程序重复处理一批数据;
if根据条件决定某段语句是否执行。
如果只使用循环,循环体中的每一轮通常做同样的事情;如果只使用 if,程序只能判断少量数据。实际问题经常需要:
逐个处理数据 → 判断当前数据 → 根据判断结果进行不同操作
例如:
- 统计
1到n中有多少个偶数; - 求
m到n之间所有奇数的和; - 在一组数中求最大值和最小值;
- 找到第一个满足条件的数后立即停止。
这些问题都需要把循环和分支组合起来。
2. 从单次判断过渡到循环判断
前面学习 if 时,程序通常只判断一个数据。本课把这个判断放进循环中,让程序逐个处理一批数据:
for (int i = 1; i <= n; i++) {
if (i % 2 == 0) {
cout << i << ' ';
}
}
循环负责不断产生或读入数据,if 负责判断当前数据。这样就能完成筛选、分类、统计和查找等任务。
二、循环与 if 的基本组合
1. 基本模式
最常见的写法是在循环体内放一个 if:
for (int i = 开始值; i <= 结束值; i++) {
if (条件) {
// 条件成立时执行
}
}
循环负责“一个一个地处理”,if 负责“判断当前数据”。
2. 最小示例:统计偶数个数
int count = 0;
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
count++;
}
}
cout << count << '\n';
输出:
5
执行过程可以理解为:
for依次让i取1到10;- 每轮用
i % 2 == 0判断i是否为偶数; - 如果是偶数,计数器
count加1; - 循环结束后,
count就是偶数的个数。
3. if 的执行流程
flowchart TD
Start([开始一轮循环]) --> Cond{"条件成立吗?"}
Cond -->|true 真| Do["执行条件分支"]
Cond -->|false 假| Skip["跳过条件分支"]
Do --> Next["执行后面的语句"]
Skip --> Next
Next --> Update["进入下一轮循环"]
classDef condition fill:#e1f5fe,stroke:#0288d1,stroke-width:2px;
classDef action fill:#fff9c4,stroke:#fbc02d,stroke-width:1px;
class Cond condition;
class Start,Do,Skip,Next,Update action;
关键理解: 循环决定“处理多少次”,
if决定“这一轮是否执行某个操作”。
4. if-else:两类情况分别处理
如果条件成立和不成立时都需要做不同的事情,可以使用 if-else:
for (int i = 1; i <= n; i++) {
if (i % 2 == 0) {
// 偶数的处理
} else {
// 奇数的处理
}
}
例如,同时统计偶数和奇数的个数:
int evenCount = 0;
int oddCount = 0;
for (int i = 1; i <= n; i++) {
if (i % 2 == 0) {
evenCount++;
} else {
oddCount++;
}
}
cout << evenCount << ' ' << oddCount << '\n';
if-else 的两个分支每轮只会执行一个,不会同时执行。
三、continue:跳过本次循环
1. 基本语法和作用
continue;
continue 的意思是:跳过本次循环中剩余的语句,直接进入下一次循环。
它不会结束整个循环,只是不处理当前这一轮后面的内容。
可以这样记:
continue = 这一轮不做了,下一轮继续
2. 示例:跳过 3 的倍数
下面的程序输出 1 到 n 中不是 3 的倍数的数:
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
if (i % 3 == 0) {
continue;
}
cout << i << ' ';
}
cout << '\n';
输入:
10
输出:
1 2 4 5 7 8 10
当 i 为 3、6、9 时,执行 continue,后面的 cout 被跳过;然后循环继续处理下一个数。
3. continue 在 for 中的执行顺序
对于下面的循环:
for (int i = 1; i <= n; i++) {
if (条件) {
continue;
}
// 其他语句
}
遇到 continue 后,执行顺序是:
跳过本轮剩余语句 → 执行 for 的更新 i++ → 再判断循环条件
因此,for 中的 continue 不会跳过 i++,通常不会因为忘记更新而死循环。
4. 用 continue 求条件和
求 1 到 10 中奇数的和,可以先跳过偶数:
int sum = 0;
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue;
}
sum += i;
}
cout << sum << '\n';
输出:
25
这里 continue 把“偶数不参与累加”表达得很清楚。
不过,下面这种写法也完全正确:
int sum = 0;
for (int i = 1; i <= 10; i++) {
if (i % 2 != 0) {
sum += i;
}
}
两种写法的结果相同。条件较简单时,直接把操作写进 if 也很自然;当需要排除的情况较多、后面的代码较长时,continue 可以减少嵌套。
5. while 中的 continue 陷阱
在 while 中,更新语句通常写在循环体内部。continue 会直接回到条件判断,如果更新语句在它后面,就可能永远执行不到。
错误示例:
int i = 0;
while (i < 10) {
if (i == 5) {
continue;
}
cout << i << ' ';
i++;
}
当 i 变成 5 时,程序执行 continue,跳过下面的 i++;下一次判断时 i 仍然是 5,于是会不断重复,形成死循环。
一种修改方法是把更新放到 continue 前面:
int i = 0;
while (i < 10) {
i++;
if (i == 5) {
continue;
}
cout << i << ' ';
}
初学阶段,如果循环次数明确,优先使用 for;如果必须在 while 中使用 continue,要检查每一条路径是否都会更新循环变量。
四、break:提前结束循环
1. 基本语法和作用
break;
break 的意思是:立即结束当前循环,执行循环后面的语句。
可以这样记:
break = 不循环了,直接退出
break 和 continue 的区别:
| 语句 | 当前这一轮 | 后续循环 | 适合场景 |
|---|---|---|---|
continue |
剩余语句不执行 | 继续 | 跳过不需要处理的数据 |
break |
立即结束 | 不再进行 | 找到答案后停止搜索 |
2. 示例:找到第一个大于 10 的数
int n;
cin >> n;
int answer = -1;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
if (x > 10) {
answer = x;
break;
}
}
if (answer == -1) {
cout << "未找到" << '\n';
} else {
cout << answer << '\n';
}
输入:
5
3 7 15 2 18
输出:
15
读到 15 后,answer 记录答案,break 立即结束循环,后面的 2 和 18 不再处理。
3. 为什么查找“第一个”要使用 break
如果不使用 break,程序可能继续向后查找,并覆盖之前记录的答案:
int answer = -1;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
if (x > 10) {
answer = x;
}
}
这段代码最后记录的是最后一个大于 10 的数,而不是第一个。
因此:
- 找第一个满足条件的数:找到后记录并
break; - 找最后一个满足条件的数:不能提前
break,每次满足条件都更新答案; - 统计所有满足条件的数:遍历全部数据,用累加器或计数器处理。
4. break 只结束当前循环
break 只结束它所在的那一层循环。如果以后学习多层循环,要注意:
break 只跳出当前所在的最内层循环
本课只使用单层循环,但提前了解这个规则,可以避免把 break 误认为“结束整个程序”。
五、经典应用一:条件求和与条件计数
1. 条件求和
条件求和就是:只把满足条件的数据加入累加器。
例如,求 m 到 n 之间所有奇数的和:
int m, n;
cin >> m >> n;
long long sum = 0;
for (int i = m; i <= n; i++) {
if (i % 2 != 0) {
sum += i;
}
}
cout << sum << '\n';
核心结构是:
遍历 → 判断 → 满足条件才累加
累加器 sum 要在循环外初始化为 0。
2. 条件计数
条件计数就是:每发现一个满足条件的数据,计数器加 1。
例如,读入 n 个整数,统计其中正数的个数:
int n;
cin >> n;
int count = 0;
for (int i = 0; i < n; i++) {
long long x;
cin >> x;
if (x > 0) {
count++;
}
}
cout << count << '\n';
这里不需要保存所有数据,因为每个数读入后马上判断、马上计数即可。
3. 同时求和和计数
同一次遍历中可以设置多个结果变量:
int n;
cin >> n;
int count = 0;
long long sum = 0;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
if (x > 0) {
count++;
sum += x;
}
}
cout << count << ' ' << sum << '\n';
一个 if 可以同时更新多个变量,只要这些变量的含义清楚即可。
4. if-else 分类统计
如果要分别统计正数、负数和零,可以使用 if-else if-else:
int positive = 0;
int negative = 0;
int zero = 0;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
if (x > 0) {
positive++;
} else if (x < 0) {
negative++;
} else {
zero++;
}
}
cout << positive << ' ' << negative << ' ' << zero << '\n';
每个数只会进入其中一个分支,因此三个计数器的总和应等于 n。
六、经典应用二:求最大值和最小值
1. “打擂台”的思想
求最大值或最小值,可以把每次读入的数据想象成参加一场擂台赛:
当前数据上场 → 和擂主比较 → 更优秀者成为新的擂主
- 求最大值时,当前更大的数成为新的“最大擂主”;
- 求最小值时,当前更小的数成为新的“最小擂主”。
代码中的 maxValue 和 minValue 就是擂主。每读入一个新数据,就用 if 比较并决定是否更新。程序不需要记住之前的所有数据,只需要记住当前擂主。
例如,依次读入:
3 7 2 9 4
求最大值时的打擂台过程如下:
| 当前读入的数据 | 比较前的擂主 | 比较结果 | 比较后的擂主 |
|---|---|---|---|
第一个数据 3 |
— | 先让 3 当擂主 |
3 |
7 |
3 |
7 更大,换擂主 |
7 |
2 |
7 |
2 不够大,擂主不变 |
7 |
9 |
7 |
9 更大,换擂主 |
9 |
4 |
9 |
4 不够大,擂主不变 |
9 |
最后的擂主 9 就是最大值。
2. 为什么不能随便把初值设为 0
错误思路:
int maxValue = 0;
如果所有数据都是负数,例如 -8 -3 -10,程序会错误地保留 0,但 0 根本不在数据中。
求最小值时也不能随便写:
int minValue = 0;
如果所有数据都是正数,例如 5 8 3,程序会错误地保留 0。
因此,初始化值必须满足下面的要求:
- 求最大值时,初值应该不大于所有可能的数据;
- 求最小值时,初值应该不小于所有可能的数据。
3. 方法一:用第一个数据作为初始擂主
当题目保证至少有一个数据时,最稳妥、最推荐的方法是先读入第一个数据:
int n;
cin >> n;
int x;
cin >> x; // 先读第一个数据
int maxValue = x; // 第一个数据成为最大擂主
int minValue = x; // 第一个数据成为最小擂主
for (int i = 1; i < n; i++) {
cin >> x;
if (x > maxValue) {
maxValue = x;
}
if (x < minValue) {
minValue = x;
}
}
cout << maxValue << ' ' << minValue << '\n';
这种方法不需要猜数据范围。无论数据全是正数、全是负数,还是正负都有,都可以正确工作。
它要求 n >= 1,因为程序需要先读入一个数据作为初始擂主。
如果数据是 -8 -3 -10 -5,使用第一个数据 -8 作为初始擂主,之后的比较过程是:
| 当前读入的数据 | 比较前的最大擂主 | 比较结果 | 比较后的最大擂主 |
|---|---|---|---|
第一个数据 -8 |
— | 先让 -8 当擂主 |
-8 |
-3 |
-8 |
-3 更大,换擂主 |
-3 |
-10 |
-3 |
-10 不够大,擂主不变 |
-3 |
-5 |
-3 |
-5 不够大,擂主不变 |
-3 |
最后最大值是 -3。这说明使用第一个数据初始化时,不怕数据全部为负数。
求最小值时,只需把比较方向改成 <:
int n;
cin >> n;
int x;
cin >> x;
int minValue = x; // 第一个数据先当最小擂主
for (int i = 1; i < n; i++) {
cin >> x;
if (x < minValue) {
minValue = x;
}
}
cout << minValue << '\n';
4. 方法二:用范围外的极值作为初始值
如果题目明确给出了数据范围,也可以把初始值设置为一个“不会被误认为答案”的极值。
例如,题目保证每个整数都在 -1e9 到 1e9 之间:
int n;
cin >> n;
int maxValue = -1e9; // 最大值擂主:比所有可能的数据都小或相等
int minValue = 1e9; // 最小值擂主:比所有可能的数据都大或相等
for (int i = 0; i < n; i++) {
int x;
cin >> x;
if (x > maxValue) {
maxValue = x;
}
if (x < minValue) {
minValue = x;
}
}
cout << maxValue << ' ' << minValue << '\n';
这里的 -1e9 和 1e9 不是“随便写的神奇数字”,而是根据题目范围选择的。可以把它们理解为:
maxValue的初值要不大于所有可能的数据;minValue的初值要不小于所有可能的数据。
如果题目使用 int,也可以使用标准库中的极值:
#include <climits>
int maxValue = INT_MIN;
int minValue = INT_MAX;
如果数据范围是 -10^18 到 10^18,就不能继续使用 int 和 1e9,应改用 long long,并选择相应范围的初值,例如:
long long maxValue = -1000000000000000000LL;
long long minValue = 1000000000000000000LL;
也可以写成:
long long maxValue = LLONG_MIN;
long long minValue = LLONG_MAX;
使用这些初值时,必须确认题目中的数据类型和范围。1e9、-1e9、1e18、-1e18 都不是可以无条件套用的固定答案。
5. 两种初始化方法怎样选择
| 方法 | 适用条件 | 优点 | 需要注意 |
|---|---|---|---|
| 第一个数据初始化 | 至少有一个数据,通常 n >= 1 |
不需要猜范围,最稳妥 | 要先读入第一个数据 |
| 范围外极值初始化 | 题目明确给出数据范围 | 代码可以统一从第一个循环开始读 | 初值必须覆盖所有合法数据 |
一般建议:
数据范围不确定 → 用第一个数据初始化
数据范围明确 → 可以用范围外的极值初始化
6. 边读边比较,不必保存全部数据
无论采用哪种初始化方式,核心都是“边读边比较”:每个数据只需要和当前擂主比较一次,不需要保存全部数据。
int n;
cin >> n;
int maxValue = INT_MIN;
int minValue = INT_MAX;
for (int i = 0; i < n; i++) {
cin >> x;
if (x > maxValue) {
maxValue = x;
}
if (x < minValue) {
minValue = x;
}
}
cout << maxValue << ' ' << minValue << '\n';
7. 同时求最大值、最小值和总和
同一次循环中可以完成多个任务:
int n;
cin >> n;
int maxValue = INT_MIN;
int minValue = INT_MAX;
long long sum = 0;
for (int i = 0; i < n; i++) {
cin >> x;
if (x > maxValue) {
maxValue = x;
}
if (x < minValue) {
minValue = x;
}
sum += x;
}
cout << maxValue << ' ' << minValue << ' ' << sum << '\n';
七、经典应用三:查找与搜索
1. 查找第一个满足条件的数
查找题通常使用一个结果变量记录答案:
int answer = -1;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
if (x > 10) {
answer = x;
break;
}
}
answer = -1 表示“目前还没有找到”。循环结束后:
answer仍为-1:没有找到;answer已被更新:找到了第一个满足条件的数。
2. 查找第一个满足条件的位置
如果题目要求输出第一个满足条件的数据出现的是第几次,可以在读入时记录循环次数:
int n;
cin >> n;
int position = -1;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
if (x > 10) {
position = i; // 第 i 次读入
break;
}
}
cout << position << '\n';
如果没有找到,position 保持为 -1。找到后使用 break,后面的数据不再处理。
3. 查找和遍历的区别
| 任务 | 是否必须看完所有数据 | 常用写法 |
|---|---|---|
| 求和 | 是 | 遍历全部数据,累加 |
| 求最值 | 是 | 遍历全部数据,比较更新 |
| 统计个数 | 是 | 遍历全部数据,满足条件就计数 |
| 找第一个 | 否 | 找到后记录并 break |
| 找最后一个 | 是 | 不 break,每次找到都更新 |
关键区别: 求和、计数和最值需要完整遍历;查找第一个满足条件的数,找到后就可以提前结束。
4. 查找时要先看输入顺序
本课先练习“读入一个数据就能判断”的查找方式。例如,题目说读入 n 个数,找第一个大于 10 的数,就可以每读一个数立即判断,找到后 break。
如果题目要求根据后面才出现的条件反复查看前面的数据,需要保存数据,这属于后续课程的内容。本课暂不展开。
八、常见错误与注意事项
1. 有编译器提示的错误
| 编号 | 错误 | 常见识别信息 | 后果 | 改正 |
|---|---|---|---|---|
| E1 | if 条件或括号没有配对 |
expected ')'、expected '}' |
分支结构无法编译 | 检查圆括号和花括号 |
| E2 | 使用没有定义的变量 | 'x' was not declared |
编译器找不到数据 | 先定义读入变量和结果变量 |
| E3 | continue 或 break 写在循环外 |
常见提示 continue statement not within a loop |
控制语句没有所属循环 | 把它放入 for 或 while 内 |
| E4 | 使用了错误的变量名 | 'maxv' was not declared 等 |
结果变量无法识别 | 检查变量名是否前后一致 |
2. 没有固定报错,但结果或运行过程不对
| 编号 | 错误 | 后果 | 改正 |
|---|---|---|---|
| E5 | 把 == 写成 = |
把判断变成赋值,条件结果异常 | 判断相等使用 == |
| E6 | while 中 continue 跳过了更新 |
循环变量不变,形成死循环 | 更新写在 continue 前,或改用 for |
| E7 | 把 break 和 continue 混用 |
可能提前结束,或没有按预期继续 | continue 是跳过本轮,break 是结束循环 |
| E8 | 最大值或最小值初始值随意写成 0 |
全为负数或全为正数时结果错误 | 用第一个数据初始化,或根据范围设置极值 |
| E9 | 使用 1e9、-1e9 却没有检查数据范围 |
合法数据可能超过初始值,结果错误 | 确认初值覆盖所有合法数据;不确定时用第一个数据 |
| E10 | 查找第一个却没有 break |
答案可能被后面的数据覆盖成最后一个 | 找到后记录答案并退出 |
| E11 | 查找最后一个却误写 break |
只检查到第一个,结果不符合题意 | 找最后一个时遍历到底 |
| E12 | 能边读边处理却保存了不必要的数据 | 代码更复杂,增加额外存储 | 读入后立即判断、统计或更新结果 |
3. continue 和 break 的快速检查
看到控制语句时,可以问自己两个问题:
这条语句是要跳过这一轮,还是要结束整个循环?
执行后,循环变量还会不会按预期变化?
如果答案是“只跳过这一轮”,使用 continue;如果答案是“后面不需要再循环”,使用 break。
4. 最值和查找的边界检查
写最值和查找程序时,重点检查:
- 数据是否至少有一个;
- 使用的是“第一个数据初始化”还是“范围外极值初始化”;
- 循环次数是否与题目给出的数据个数一致;
- 题目要找第一个还是最后一个;
- 输出的是数值本身,还是它出现的次数或位置。