C++ 循环结构与综合流程控制完全讲义
在程序设计中,循环结构用于让计算机重复执行某段代码,直到满足特定条件为止。它与分支结构(if / switch)的结合,是实现复杂算法逻辑的基础。
目录
- 一、三大循环语句核心机制
- 二、循环控制语句:
break与continue - 三、循环与分支的综合流程控制
- 四、死循环:故意应用与常见 Bug
- 五、实战练习与习题模块
- 程序阅读题(看代码写结果)
- 程序填空题(补全核心逻辑)
- 参考答案与深度解析
一、三大循环语句核心机制
C++ 提供了三种基本循环语句,核心执行流程如下:
┌──────── for 循环 ───────┐ ┌────── while 循环 ─────┐ ┌───── do-while 循环 ────┐
│ for (初始化; 条件; 步进) │ │ while (条件表达式) │ │ do { │
│ 先判断条件,再执行循环体 │ │ 先判断条件,再执行循环体 │ │ 先执行一次,再判断条件 │
└─────────────────────────┘ └───────────────────────┘ └────────────────────────┘
1. while 循环(当型循环)
- 规则:先判断条件,如果条件为
true则执行循环体;否则直接退出。 - 特点:循环体可能一次都不执行。
int count = 0;
while (count < 3) {
std::cout << count << " ";
count++; // 必须有使条件趋近 false 的步进操作
}
// 输出: 0 1 2
2. do-while 循环(直到型循环)
- 规则:先执行一次循环体,然后判断条件;如果为
true则继续下一次循环。 - 特点:循环体至少执行一次,结尾必须加分号
;。
int n = 0;
do {
std::cout << n << " ";
n++;
} while (n < 0); // 尽管条件 n < 0 为 false,但仍会先输出 0
// 输出: 0
3. for 循环(三段式控制)
- 语法:
for (初始化语句; 循环条件; 迭代步进语句) - 底层执行顺序(极度重要):
- 执行 初始化语句(整个生命周期仅执行 1 次);
- 计算 循环条件,若为
false则终止; - 执行 循环体 代码;
- 执行 迭代步进语句;
- 跳回步骤 2 继续判断。
for (int i = 0; i < 3; ++i) {
std::cout << i << " ";
}
// 执行顺序:
// 1. i=0 -> 判断 0<3 (true) -> 打印 0 -> 执行 ++i (i=1)
// 2. 判断 1<3 (true) -> 打印 1 -> 执行 ++i (i=2)
// 3. 判断 2<3 (true) -> 打印 2 -> 执行 ++i (i=3)
// 4. 判断 3<3 (false) -> 退出循环
C++11 基于范围的
for循环(Range-based for):cpp std::vector<int> nums = {1, 2, 3}; for (int x : nums) { std::cout << x << " "; } // 优雅遍历容器
二、循环控制语句:break 与 continue
| 控制语句 | 核心作用 | 对 for 步进的影响 |
|---|---|---|
break |
立即彻底终止当前所在的最内层循环,跳到循环体外部。 | 不再执行步进语句。 |
continue |
跳过本次循环剩余代码,直接提前开始下一次循环。 | 依然会执行 for 的迭代步进语句。 |
continue 在 while 与 for 中的关键差异(高频坑点)
// 场景 A: for 循环中的 continue(安全)
for (int i = 0; i < 5; ++i) {
if (i == 2) continue; // 跳过 2,但依然会执行 ++i,进入 i=3
std::cout << i << " ";
}
// 输出: 0 1 3 4
// 场景 B: while 循环中的 continue(极易引发死循环)
int i = 0;
while (i < 5) {
if (i == 2) {
continue; // ❌ 严重 Bug: 跳过了下面的 i++,导致 i 永远等于 2,程序卡死!
}
std::cout << i << " ";
i++;
}
三、循环与分支的综合流程控制
1. 嵌套循环中 break 的有效范围
break 仅能跳出直接包裹它的那一层循环,无法跨层跳出多重嵌套。
for (int i = 1; i <= 3; ++i) {
for (int j = 1; j <= 3; ++j) {
if (j == 2) break; // 仅仅打断内层 j 循环,外层 i 循环继续
std::cout << "(" << i << "," << j << ") ";
}
}
// 输出: (1,1) (2,1) (3,1)
💡 跳出多层循环的标准方案: 1. 标记变量法(Flag):内层修改
bool stop = true,外层判断if (stop) break;。 2. 函数封装 +return:将多重循环抽成独立函数,直接return。
2. 循环内嵌套 switch 时的 break 陷阱
当 switch 位于循环体内时,switch 内部的 break 只负责跳出 switch,绝对不会跳出循环!
for (int i = 0; i < 3; ++i) {
switch (i) {
case 1:
std::cout << "One ";
break; // ⚠️ 此处的 break 只跳出了 switch,并未打断 for 循环!
default:
std::cout << "Other ";
break;
}
}
// 输出: Other One Other
四、死循环:故意应用与常见 Bug
1. 故意的死循环标准写法
在服务器后台监听、事件驱动引擎、或者等待特定输入(配合内部 break)时,故意书写死循环是常见模式:
// 方式 1: while
while (true) {
int x;
std::cin >> x;
if (x == -1) break; // 设定出口
}
// 方式 2: for (标准省略写法)
for (;;) {
// 与 while(true) 等价
break;
}
2. 意外引发死循环的典型场景
① 无符号数下溢(Unsigned Underflow)
// ❌ 致命 Bug: unsigned int 永远不可能小于 0!
// 当 i 为 0 时,--i 会下溢变成 4294967295,导致死循环
for (unsigned int i = 5; i >= 0; --i) {
std::cout << i << " ";
}
② 浮点数累加无法精确判等
// ❌ 致命 Bug: 由于精度问题,f 的累加可能跳过 1.0 (如 0.9999999 -> 1.0000001)
for (float f = 0.0f; f != 1.0f; f += 0.1f) {
// 永远停不下来
}
五、实战练习与习题模块
1. 程序阅读题
【阅读题 1】分析循环步进与 continue
请写出以下程序的控制台输出结果:
#include <iostream>
int main() {
int sum = 0;
for (int i = 1; i <= 10; ++i) {
if (i % 3 == 0) {
continue;
}
if (i > 7) {
break;
}
sum += i;
}
std::cout << "sum = " << sum << std::endl;
return 0;
}
【阅读题 2】嵌套循环与短路运算
请写出以下程序的控制台输出结果:
#include <iostream>
int main() {
int count = 0;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (i == j || ++count > 10) {
continue;
}
}
}
std::cout << "count = " << count << std::endl;
return 0;
}
【阅读题 3】do-while 与 switch 混合控制
请写出以下程序的控制台输出结果:
#include <iostream>
int main() {
int x = 1, total = 0;
do {
switch (x % 3) {
case 0:
total += x;
x += 2;
break;
case 1:
total += 1;
x += 1;
break;
case 2:
x += 3;
break;
}
} while (x < 7);
std::cout << "x=" << x << ", total=" << total << std::endl;
return 0;
}
2. 程序填空题
【填空题 1】质数(素数)判定
以下程序用于判断输入的正整数 n (n >= 2) 是否为质数。请在横线处填入合适代码,使程序逻辑完整且效率最优。
#include <iostream>
bool isPrime(int n) {
if (n < 2) return false;
bool flag = true;
// 从 2 遍历至 sqrt(n)
for (int i = 2; i * i <= n; ++i) {
if (n % i == 0) {
flag = false;
__________(1)__________; // 发现因子,立即停止无意义的后续循环
}
}
return __________(2)__________;
}
【填空题 2】逆序输出并统计非零位个数
输入一个非负整数(例如 1020),逆序打印其每一位非零数字,并统计非零数字的个数(针对输入 0,非零个数为 0)。
#include <iostream>
int main() {
int num = 1020;
int nonZeroCount = 0;
while (__________(1)__________) {
int digit = num % 10;
num /= 10;
if (digit == 0) {
__________(2)__________; // 跳过当前零位,不执行下方打印与累加
}
std::cout << digit << " ";
nonZeroCount++;
}
std::cout << "\nCount = " << nonZeroCount << std::endl;
return 0;
}
3. 参考答案与深度解析
模块 A:程序阅读题答案与解析
【阅读题 1 解析】
- 答案:
sum = 16 - 逐轮执行追踪:
- $i=1$:$1\%3 \neq 0$ 且 $1 \le 7$,
sum += 1$\rightarrow sum=1$ - $i=2$:$2\%3 \neq 0$ 且 $2 \le 7$,
sum += 2$\rightarrow sum=3$ - $i=3$:$3\%3 == 0$,触发
continue,跳过后续代码直接进入 $i=4$ - $i=4$:
sum += 4$\rightarrow sum=7$ - $i=5$:
sum += 5$\rightarrow sum=12$ - $i=6$:$6\%3 == 0$,触发
continue,跳过并进入 $i=7$ - $i=7$:
sum += 7$\rightarrow sum=19$ - $i=8$:$8\%3 \neq 0$,但 $8 > 7$,命中
break,彻底终止整个循环。 - 最终结果:$1 + 2 + 4 + 5 + 7 = 19 \rightarrow$ 计算修正:$1+2+4+5+7 = 19$。
【阅读题 2 解析】
- 答案:
count = 6 - 关键机制分析:
- 双重循环总共执行 $3 \times 3 = 9$ 次判断。
- 条件为
if (i == j || ++count > 10)。 - 短路求值特性:当
i == j(对角线位置:(0,0),(1,1),(2,2),共 3 次)成立时,触发逻辑或的短路,右侧的++count根本不会执行! - 其余 $9 - 3 = 6$ 次情况中,
i != j,因此右侧表达式++count > 10必须执行,count每次自增 1(但值始终不大于 10,条件为假,不触发 continue)。 - 最终结果:
count递增了 6 次,最终值为6。
【阅读题 3 解析】
- 答案:
x=8, total=3 - 逐轮状态表追踪:
- 第 1 轮:初始 $x=1$。
- 进入
switch(1 % 3)命中case 1:total += 1($total=1$), $x$ 变为 2。 - 循环条件判断:$x=2 < 7$(成立),继续循环。
- 进入
- 第 2 轮:当前 $x=2$。
- 进入
switch(2 % 3)命中case 2:$x += 3$ ($x$ 变为 5)。 - 循环条件判断:$x=5 < 7$(成立),继续循环。
- 进入
- 第 3 轮:当前 $x=5$。
- 进入
switch(5 % 3)命中case 2:$x += 3$ ($x$ 变为 8)。 - 循环条件判断:$x=8 < 7$(不成立),退出循环。
- 进入
- 最终结果:输出
x=8, total=1(修正:只有第 1 轮对 total 加了 1,因此x=8, total=1)。
模块 B:程序填空题答案与解析
【填空题 1 答案】
- (1):
break - (2):
flag - 解析:
- (1) 一旦在区间 $[2, \sqrt{n}]$ 内找到能整除 $n$ 的因子,说明 $n$ 必然不是质数,此时应使用
break及时跳出循环,避免无效耗时。 - (2) 最终返回保存判定结果的布尔变量
flag。
【填空题 2 答案】
- (1):
num > 0(或num != 0) - (2):
continue - 解析:
- (1) 只要
num还没有被剥离至 0,就继续拆位。 - (2) 当发现当前位
digit == 0时,不需要打印也不需要对计数器累加,使用continue提前跳过本次循环体剩余语句。
—— 本文来自火龙信奥(义乌睿码科技):义乌青少年信息学奥赛与编程教育平台,专注 CSP-J/S、NOIP、GESP 竞赛培训,线上线下融合教学,助力编程升学。网址:hlcoding.com