6、循环结构:while、do-while、for、循环嵌套、break与continue
循环,说白了就是让计算机反复干同一件事。你想想看,如果让你手动输出1到100,你肯定烦死了。但计算机不会烦,它只会乖乖听你的话。这就是循环存在的意义。
我个人习惯把循环分成三类:先判断再执行的while、先执行再判断的do-while、以及计数型的for。每种都有它的脾气,用错了地方,代码就会变得别扭。
6.1 while循环——先看门再进门
while的语法很简单:
while (条件表达式) {
// 循环体
}
每次循环开始前,先检查条件。条件为真,就进去执行一次;执行完再回来检查。条件为假,直接跳过。
我在项目中遇到过一个问题:用while循环读取文件,结果文件指针没更新,直接死循环了。CPU飙到100%,服务器差点挂掉。嗯,这里要注意——循环体内一定要有改变条件的语句,否则就是死循环。
核心要点:while适合"不知道要循环多少次,但知道什么时候停"的场景。比如等待用户输入、读取数据流。
6.2 do-while循环——先进门再看门
do-while和while的区别就一句话:至少执行一次。
do {
// 循环体
} while (条件表达式);
先执行一次循环体,再判断条件。条件为真就继续,为假就退出。
什么时候用do-while?我举个例子:你写一个菜单程序,让用户选择操作。不管用户选什么,菜单至少得显示一次吧?这时候do-while就派上用场了。
小技巧:do-while在实际项目中用得不多,但一旦用上,往往是因为while解决不了"至少执行一次"的需求。别硬用,也别不用。
6.3 for循环——计数器的王者
for循环是我用得最多的。它的结构很清晰:
for (初始化; 条件; 更新) {
// 循环体
}
三个部分各司其职:
- 初始化:循环开始前执行一次,通常用来定义计数器
- 条件:每次循环前检查,真则继续,假则退出
- 更新:每次循环结束后执行,通常用来改变计数器
举个例子,遍历数组:
int arr[] = {10, 20, 30, 40, 50};
for (int i = 0; i < 5; i++) {
cout << arr[i] << endl;
}
我个人习惯把for循环用在"明确知道循环次数"的场景。比如遍历数组、处理固定长度的数据。
注意:for循环的三个部分都可以省略,但分号不能省。写成for(;;)就是死循环。我曾经在代码审查时看到有人这么写,还忘了加break,结果...你懂的。
6.4 循环嵌套——循环里面套循环
循环嵌套,说白了就是一个循环的循环体里,又有一个循环。最常见的就是处理二维数组:
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
外层循环控制行,内层循环控制列。这个模式你以后会经常见到。
我在项目中遇到过一个问题:三层循环嵌套,数据量一大,程序直接卡死。后来一分析,时间复杂度是O(n³),数据量上万就扛不住了。所以我的建议是——嵌套层数尽量控制在两层以内,超过三层就要考虑优化了。
性能提醒:循环嵌套的时间复杂度是相乘的关系。两层循环是O(n²),三层是O(n³)。数据量大的时候,差别是几何级的。
6.5 break与continue——循环的刹车和跳过
这两个关键字,一个用来提前退出,一个用来跳过本次。
break:直接跳出当前循环,不再执行后续的迭代。
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // i等于5时直接退出循环
}
cout << i << " ";
}
// 输出:0 1 2 3 4
continue:跳过本次循环的剩余部分,直接进入下一次迭代。
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
continue; // 偶数跳过,不输出
}
cout << i << " ";
}
// 输出:1 3 5 7 9
我曾经在写网络通信模块时,用break来处理超时。如果规定时间内没收到响应,直接break跳出等待循环,避免程序卡死。continue则常用于过滤数据——比如处理一批数据时,跳过不合法的记录。
经验之谈:break和continue只作用于当前层的循环。如果你在嵌套循环里想跳出外层,需要用goto或者单独设置标志变量。我个人不太推荐goto,但偶尔用一次也不是什么大罪。
6.6 知识体系总览
下面这张图,把本章的核心逻辑串起来了:
6.7 避坑指南
最后,我把自己踩过的坑总结一下:
| 常见错误 | 后果 | 解决方法 |
|---|---|---|
| 忘记更新循环变量 | 死循环,CPU 100% | 检查循环体内是否有改变条件的语句 |
| 循环嵌套过深 | 性能急剧下降 | 尽量控制在两层以内,或改用其他算法 |
| break/continue用错层 | 逻辑错误,难以排查 | 明确break/continue只作用于当前循环 |
| for循环分号位置错 | 编译错误或死循环 | 记住for(;;)三个部分,分号不能少 |
我曾经在写一个数据处理模块时,三层循环嵌套,中间还用了continue。结果调试了一下午,才发现continue跳到了内层循环,而不是外层。从那以后,我写嵌套循环时都会格外小心,必要时用注释标明每一层的作用。
好了,循环这块就讲到这里。记住一句话:循环是工具,不是目的。用对了,代码简洁高效;用错了,代码又臭又长。多写多练,慢慢就有感觉了。