☰
C语言分支与循环语句练习:核心易错点与调试技巧
2026/10/21 18:17:05 网站建设 项目流程

1. 为什么分支与循环练习是C语言的第一道分水岭

后台经常有人问我类似的问题:语法书看了两三遍,switch和for的格式也能默写,可一到自己动手写程序就卡壳,不知道从哪下手。这种状态我太熟悉了,几乎所有学C语言的人都在这道坎上栽过跟头,我当年也不例外。

其实原因并不复杂。分支与循环语句本身只是三个关键字(if、for、while)外加一个switch,语法规则半小时就能讲完。但它们背后牵扯的是一种叫“程序执行流控制”的思维方式——你得学会像计算机一样,按条件逐条判断、按次数反复执行,并且在整个过程中始终清楚“现在程序走到哪一行了,变量的值变成多少了”。这种思维没法靠背语法获得,只能靠大量练习来建立。

所以“C语言-分支与循环语句练习”这个标题看着朴素,实际分量很重。它对应的是C语言学习路径中承上启下的关键节点:承上,需要你掌握变量、数据类型、运算符和输入输出;启下,后面学函数、数组、指针时,几乎每个模块都要用到分支和循环来控制逻辑。

我把这一关称为分水岭,原因也在这里。能独立完成一批高质量练习的人,后面学数组和指针会顺畅得多;如果这里靠抄答案混过去,后面的知识会越来越吃力,直到彻底听不懂。

这一篇我就用做练习的方式,把这套东西彻底过一遍。所有的思路、代码、易错点都是我实测或教学中反复验证过的,照着练、照着改,比闷头刷十遍语法书都管用。

2. 分支语句的练习:先搞清楚if和switch各自该用在什么场景

2.1 if-else嵌套里最常见的悬挂else问题

分支语句的练习,第一步不是写代码,而是理解if-else的组合规则。这里有个几乎所有教材都会讲、但初学者仍然反复踩的坑——悬挂else(dangling else)。

来看这段代码:

#include <stdio.h> int main() { int score = 85; if (score >= 60) if (score >= 90) printf("优秀\n"); else printf("中等\n"); return 0; }

请问else匹配的是哪个if?很多初学者会以为是外层那个score >= 60,于是认为输出应该是“中等”。但实际上,C语言规定else总是与离它最近的尚未匹配的if结合,所以这段代码里的else匹配的是内层score >= 90。

也就是说,输入的85分根本不会输出任何内容——因为85不满足score >= 90这个内层条件,内层if-else整体被跳过。

这个问题在编码规范里有个标准解法:内层if一定要加大括号。

#include <stdio.h> int main() { int score = 85; if (score >= 60) { if (score >= 90) printf("优秀\n"); else printf("中等\n"); } return 0; }

加了花括号之后,else的归属一眼就能看出来。我在练习时给自己定了一条铁律:凡是if或else后面跟着另一条if语句,这条if必须用大括号包起来,永远不要依赖默认匹配规则。

2.2 switch语句的穿透陷阱与正确退出姿势

switch是分支练习里另一个容易出问题的地方。它的语法比if简洁,但有一个让新手防不胜防的坑——case穿透(fall-through)。

先看这段代码:

#include <stdio.h> int main() { int day = 3; switch (day) { case 1: printf("星期一\n"); case 2: printf("星期二\n"); case 3: printf("星期三\n"); default: printf("未知日期\n"); } return 0; }

初学时会理所当然地认为输出是“星期三”。实际上,程序从case 3进入后,会一路执行下去,输出“星期三”后又输出“未知日期”。因为switch只负责定位入口,不会自动跳出。

解决办法是每个case分支结束时加break:

#include <stdio.h> int main() { int day = 3; switch (day) { case 1: printf("星期一\n"); break; case 2: printf("星期二\n"); break; case 3: printf("星期三\n"); break; default: printf("未知日期\n"); break; } return 0; }

那穿透是不是完全没有用?也不是。我在做练习时专门试过一个经典场景:根据月份输出天数,其中2月单独处理,4/6/9/11月是30天,其余是31天。这时候故意利用穿透反而能让代码更精简:

#include <stdio.h> int main() { int month = 5; switch (month) { case 2: printf("28或29天\n"); break; case 4: case 6: case 9: case 11: printf("30天\n"); break; default: printf("31天\n"); break; } return 0; }

所以我对switch的建议是:默认每个case都要加break,除非你明确知道自己要利用穿透。这属于“显式优于隐式”的编码习惯,能省掉后面不少调试时间。

2.3 分支语句练习的两个典型题

基础规则搞清楚了,就得用题目来检验。这里有两个我反复推荐的练习,难度不高但信息密度大。

第一个是成绩等级转换。输入百分制成绩,90分以上为A,80到89为B,70到79为C,60到69为D,60以下为E。

#include <stdio.h> int main() { int score; printf("请输入成绩:"); scanf("%d", &score); if (score < 0 || score > 100) { printf("输入不合法\n"); } else if (score >= 90) { printf("A\n"); } else if (score >= 80) { printf("B\n"); } else if (score >= 70) { printf("C\n"); } else if (score >= 60) { printf("D\n"); } else { printf("E\n"); } return 0; }

注意这里有个细节:我把score < 0 || score > 100的判断放最前面,先把非法输入拦下来。这是我后来习惯性保留的好习惯——分支判断的顺序本身也是逻辑设计的一部分,非法情况不处理,后面的等级判断就全乱套。

第二个是判断闰年。闰年的规则是:能被4整除但不能被100整除,或者能被400整除。

#include <stdio.h> int main() { int year; printf("请输入年份:"); scanf("%d", &year); if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) { printf("%d是闰年\n", year); } else { printf("%d不是闰年\n", year); } return 0; }

这道题的重点不是if语法,而是逻辑表达式的组合。很多人在纸上能写对闰年规则,一翻译成代码就漏括号、用错运算符优先级。&&优先级高于||,但为了可读性,我还是建议把嵌套条件用括号明确标出来。

3. 循环语句练习:for、while、do-while的等价性、适用边界与死循环根源

3.1 三种循环语句怎么选

循环练习是整个分支循环练习中最核心的部分。因为分支本质上是“一次性的判断”,而循环是“判断 + 反复执行”,状态量更复杂,出错概率也成倍上升。

先看三种循环的等价性。下面的代码分别用三种循环求1到100的和:

#include <stdio.h> int main() { // for循环 int sum1 = 0; for (int i = 1; i <= 100; i++) { sum1 += i; } printf("for循环结果:%d\n", sum1); // while循环 int sum2 = 0, j = 1; while (j <= 100) { sum2 += j; j++; } printf("while循环结果:%d\n", sum2); // do-while循环 int sum3 = 0, k = 1; do { sum3 += k; k++; } while (k <= 100); printf("do-while循环结果:%d\n", sum3); return 0; }

三种方式结果都是5050。但要注意,它们只在“循环次数已知且至少需要执行一次”的场景下完全等价。do-while有个特殊之处:它无论如何都会先执行一次循环体,再来判断条件。这个特性在某些“必须先执行再判断”的场景里非常有用,比如做菜单选择时先显示菜单让用户输入,再判断是否退出。

我实践中的选型逻辑大致是这样:

  • 已知循环次数,或者有明确的“从哪到哪”的范围,优先用for。
  • 只知道循环终止条件、不知道具体次数,用while。
  • 不管条件是否成立,循环体都必须先执行一次的,用do-while。

拿这个标准去套题目,选型基本不会错。

3.2 死循环的真正根源:循环变量更新呗遗忘

死循环几乎是每个初学者都会遇到的第一个“程序跑飞”体验。对着屏幕干瞪眼,程序也不报错,就是停不下来。

死循环的根源,绝大多数时候不是条件写错,而是循环变量在循环体里没有正确更新。看这段:

#include <stdio.h> int main() { int i = 1; while (i <= 10) { printf("%d\n", i); // 忘了写 i++ } return 0; }

i永远等于1,条件永远成立,程序永远不退出。这在桌面程序里就是黑窗口卡死,得强制关闭;在嵌入式和服务器代码里如果出现类似的死循环,后果严重得多。

我的排查习惯是这样的:一旦程序陷入死循环,先不急着按Ctrl+C,而是盯着循环体检查三个点——第一,循环变量定义在哪;第二,循环条件里用到的变量有哪些;第三,循环体里这些变量到底有没有变。90%以上的死循环,都能在这个检查过程中发现问题。

3.3 循环练习的典型题目:最大公约数与逆序输出

循环判断的标准题目,我体会下来最有效的是这两道。

第一道是求最大公约数,用辗转相除法(欧几里得算法)。这个算法本身就是“重复做同一件事直到条件满足”的绝佳案例。

#include <stdio.h> int main() { int a, b; printf("请输入两个正整数:"); scanf("%d %d", &a, &b); while (b != 0) { int temp = b; b = a % b; a = temp; } printf("最大公约数是:%d\n", a); return 0; }

这里循环的次数完全由输入决定,无法预先知道,非常适合while循环。而且这个题目有个好处:它能逼着你理解“循环不仅是算加法,更是反复执行同一个变换规则直到收敛”。

第二道是逆序输出一个正整数。比如输入12345,输出54321。

#include <stdio.h> int main() { int n; printf("请输入一个正整数:"); scanf("%d", &n); while (n > 0) { printf("%d", n % 10); n = n / 10; } printf("\n"); return 0; }

这道题的关键是理解“取末位(模10)后去掉末位(整除10)”这个操作序列会随着循环不断推进。每循环一次,数字缩短一位,直到变成0。很多人在纸上模拟一遍就能懂,但一上来就写代码反而容易卡住——我建议遇到这种题目先在纸上把过程画出来,再翻译成代码。

4. 分支与循环的组合实战:三大经典题型的完整拆解

4.1 素数判断:break在循环中的正确用法

分支和循环单独练熟了,就该做组合题。组合题的核心是“在循环过程中根据条件改变执行流向”,这里最经典的当属素数判断。

素数定义是:大于1的自然数,只能被1和自身整除。编程实现的思路很直接:从2试到n-1,只要找到能整除的,就不是素数。

#include <stdio.h> int main() { int n, i; printf("请输入一个正整数:"); scanf("%d", &n); if (n <= 1) { printf("不是素数\n"); return 0; } for (i = 2; i < n; i++) { if (n % i == 0) { printf("不是素数\n"); return 0; } } printf("是素数\n"); return 0; }

这段代码里有两个细节值得注意。第一个是return 0的用法——一旦在循环中判断出不是素数,立即结束整个程序,不再继续空转。第二个是循环条件i < n,其实可以优化成i * i <= n,因为如果n有大于sqrt(n)的因数,那必然也有小于sqrt(n)的因数。练习时先用笨办法写对,再考虑优化,这个顺序别反了。

4.2 九九乘法表:嵌套循环的输出格式控制

嵌套循环是分支循环练习里最容易让人头晕的部分。原因很简单:外层循环每执行一次,内层循环要完整执行一轮,你在心里同时要维护两个循环变量的状态。

九九乘法表是练嵌套循环的最佳题目之一:

#include <stdio.h> int main() { for (int i = 1; i <= 9; i++) { for (int j = 1; j <= i; j++) { printf("%d*%d=%-2d ", j, i, i * j); } printf("\n"); } return 0; }

这题的关键有三个。第一,内层循环的终止条件j <= i,让内层循环范围随外层i变化,形成三角形状。第二,内层循环结束后要输出换行,这个换行必须放在内层循环之外、外层循环之内,位置一错就全乱。第三,%-2d这个格式控制符能保证左对齐占两位,让输出排列整齐。

我在教学时发现,多数人第一次独立写这题会卡在换行位置。其实判断逻辑很简单:内层循环负责打印一行中的全部内容,外层循环负责控制行数;换行是“一行结束”的标志,自然属于外层循环的职责。

4.3 水仙花数:把分支判断嵌入循环体

水仙花数指一个三位数,其各位数字的立方和等于该数本身。比如153 = 1³ + 5³ + 3³。

这题的综合度比前两道都高,因为它需要你先拆解一个三位数的百位、十位、个位,再在循环里做条件判断。

#include <stdio.h> int main() { for (int n = 100; n <= 999; n++) { int a = n / 100; int b = n / 10 % 10; int c = n % 10; if (a * a * a + b * b * b + c * c * c == n) { printf("%d\n", n); } } return 0; }

我在练习这道题时最大的体会是:循环体的第一步往往不是计算,而是“把高维信息拆成可计算的低维单元”。拿三位数来说,不拆出百十个位,你根本没法计算立方和。这种“先拆解再计算”的思路,后面处理数组和结构体时同样适用。

5. 循环控制进阶:break、continue与goto的适用边界

5.1 break与continue的语义差异及典型场景

循环里有两个改变执行流的关键字——break和continue。很多初学者分不清它们的区别,其实一句话就能说透:break是“结束整个循环”,continue是“跳过本次循环的剩余部分,直接进入下一次迭代”。

看代码更直观:

#include <stdio.h> int main() { // break:输出1 2 3 for (int i = 1; i <= 10; i++) { if (i == 4) break; printf("%d ", i); } printf("\n"); // continue:输出1 2 3 5 6 7 8 9 10 for (int i = 1; i <= 10; i++) { if (i == 4) continue; printf("%d ", i); } printf("\n"); return 0; }

一个常见的实际场景是:从键盘读入一批正整数,过滤掉所有偶数,只打印奇数,读到负数时终止。

#include <stdio.h> int main() { int num; while (1) { scanf("%d", &num); if (num < 0) break; if (num % 2 == 0) continue; printf("%d ", num); } printf("\n"); return 0; }

这里while(1)是故意构造的“无限循环”,退出完全依赖循环体内的break,这种写法在处理“不知道什么时候结束”的输入流时非常常见。

5.2 嵌套循环里的break只跳出一层,怎么突破

嵌套循环中有一个让很多人抓狂的问题:内层循环里的break,只会跳出内层这层循环,外层循环纹丝不动。

比如你想在一个二维矩阵里找到第一个负数,并输出它的位置后停止全部循环:

#include <stdio.h> int main() { int matrix[3][3] = { {1, 2, 3}, {4, 5, -6}, {7, 8, 9} }; int found = 0; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (matrix[i][j] < 0) { printf("找到负数:%d,位置(%d,%d)\n", matrix[i][j], i, j); found = 1; break; // 只跳出内层循环 } } if (found) break; // 外层循环判断后退出 } return 0; }

这里用了一个辅助变量found来“接力传递”跳出信号。内层break后,外层立刻检查found的值,一旦为真就用第二个break结束整个双重循环。这是处理多级跳出最朴素的方案,我一直建议优先使用。虽然有的环境支持用goto直接跳到某个标签位置,但那属于“外挂大招”,代码结构会被破坏,能不用尽量不用。

5.3 实际工作中goto为何被谨慎使用

既然提到goto,就顺便说清楚它的位置。goto可以做无条件跳转,配合标签能实现任意跳转,看起来很灵活。但因为goto能跨过正常执行流直接跳到另一处,会让代码的可读性和可维护性急剧下降——所谓“面条代码”(spaghetti code),就是这么产生的。

我见过一些项目明确禁止使用goto,也见过部分内核代码为了统一清理出口用有限度的goto,这个争论长期存在。对于学习阶段,我的建议是:goto可以了解,甚至可以在练习里写一写,感受一下它的机制。找循环嵌套的退出场景,尝试用标号+goto的写法实现一次,加深理解。但到了正式写项目代码时,优先用break加辅助变量的方式实现跳出,除非某些特殊场景下goto让意图更清晰。

6. 练习题的调试方法论:如何在没有IDE的情况下快速定位逻辑错误

6.1 printf大法:最原始的调试方式依然最有效

很多初学者一遇到程序输出不对,第一反应是“代码我写了啊,编译器也没报错,就是结果不对”。这其实说明编译没问题,纯逻辑有问题。这时最该做的是在关键位置插入printf,把中间变量的值打印出来。

比如求阶乘结果不对,你可以在循环里打印每一步的i和sum:

#include <stdio.h> int main() { int n = 5; int sum = 1; for (int i = 1; i <= n; i++) { sum *= i; printf("i=%d, sum=%d\n", i, sum); } printf("结果:%d\n", sum); return 0; }

从输出里你能非常直观地看到阶乘在每一步的计算变化。如果某一步开始值不对,问题就锁定在那一步上——可能是循环边界错了,也可能是累乘逻辑错了。我调试分支循环练习题时,用printf的频率比断点还高,因为这类题目的逻辑链条短,printf的即时输出足以覆盖。

6.2 变量追踪四要素:初值、变化、边界、复位

在循环练习中排查逻辑错误,我总结了一套“变量追踪四要素”:

  1. 初值:循环变量和累加/累乘变量的初始值设对了吗?是从1开始还是从0开始?
  2. 变化:每次循环结束后,变量按预期变化了吗?是增加还是减少,步长是多少?
  3. 边界:循环终止条件与预期一致吗?是多循环了一次还是少循环了一次?
  4. 复位:如果外层循环里嵌套了内层循环,内层循环的累加变量在外层每次进入时都正确初始化了吗?

这四要素里,第四点最容易踩坑。举个例子,你要求1到5每个数字的累加和到该数字为止,写成这样就是错的:

#include <stdio.h> int main() { for (int i = 1; i <= 5; i++) { int sum = 0; // 如果放在外层循环外,结果就全乱了 for (int j = 1; j <= i; j++) { sum += j; } printf("1到%d的和:%d\n", i, sum); } return 0; }

sum的定义位置一旦放错,内层循环累积的数值就会“跨行”污染下一轮计算。这个教训来自一次我写统计表格的程序,整个表的值全错,最后发现是汇总变量没在外层循环开头清零。从那以后,凡是嵌套循环,我写代码时都会刻意检查“内层循环用到的累加变量,是否在外层循环的每次迭代里重置过”。

6.3 边界测试用例:循环练得够不够扎实,看你会不会测边界

写循环练习时,很多人只测正常情况,比如输入5求1到5的和,得到15就完事了。但真正能检验代码质量的是边界情况。

拿素数判断来说,至少要测这些输入:

输入预期结果实测要点
1不是素数检查n<=1的特殊分支
2是素数检查最小的素数是否被正确处理
3是素数奇数边界
4不是素数第一个非素数的偶数
9不是素数平方数,且正好能被3整除
97是素数较大的素数

我练习时有个习惯:每个题目写完后,至少构造三组测试输入——正常值、边界值、非法值。正常值验证核心逻辑,边界值验证临界条件是否写对,非法值验证程序是否具备简单的容错能力。这种习惯在现代面试和实际工程里也很有用,因为在真实项目中,用户从来不会按你的预期输入。

7. 综合练习建议:从语法模仿到独立编码的进阶路径

分支与循环的练习,最终要落到一个“从模仿到独立”的过程上。我身边的初学者通常经过三个阶段。

第一个阶段是抄代码。看到题目,先抄一遍参考答案,然后一句一句地注释,把每一行在做什么写清楚。注意这一步不是背答案,而是理解代码的结构骨架——哪个变量是干什么的,哪个条件决定什么走向。

第二个阶段是改代码。给参考答案增加功能,或者修改条件参数,观察输出变化。比如九九乘法表改成输出倒三角,素数判断改成判断指定区间内所有素数。改代码的过程中,你会被迫理解原代码哪些部分是可替换的,哪些部分是核心结构动不得。

第三个阶段是脱离参考答案独立编码。拿到题目后先不开电脑,用纸笔写出核心伪代码,再翻译成C语言。实在写不下去,允许回头看答案,但看完必须合上重新写一遍。独立编码的练习量不需要很多,每天一题,坚持两周,效果远好过一晚上刷十道题。

我这些年带学习者的体会是:分支与循环不是背出来的,而是调试出来的。错误代码写一遍、调一遍、改一遍之后,印象远比照着正确答案抄十遍深。所以别怕写错,也别老想着一次写对,把报错和调试当成练习的一部分,这一关才能真正过。

如果你手头没有合适的练习题,我建议按这个顺序自己组合:先从判断闰年、成绩等级开始,接着做1到100求和、乘法表、素数输出,最后一口气挑战水仙花数、逆序输出、最大公约数这类综合题。顺序有讲究:先分支后循环、再组合,循序渐进,越往后越接近现实中真正的编程任务。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询