资讯详情

资讯详情

C语言循环控制结构全解析:while、do-while、for与break用法

说实话C语言学到循环这一章才算真正开始编程。前面那些变量、运算符、分支语句说到底都是在命令计算机做一件件具体的事而循环控制结构是第一次让程序自己重复劳动也是第一次逼着你用机器的视角去思考问题。很多初学者在这一章卡住不是语法背不下来而是思维方式还没换过来——这恰恰是本篇想要解决的问题。这篇文章把 while、do-while、for 三种循环以及 break、continue 这些跳转控制逐层拆开配合可以直接上机跑的代码示例讲清楚每个结构背后的设计逻辑和适用场景也把死循环、边界条件、循环变量作用域这些入门阶段最容易踩的坑一并排一遍。适合刚学完顺序结构和分支结构、准备进入循环部分的自学者也适合正在给初学者讲这一章的半桶水师兄师姐们用来参考。1. 循环不是少写几行代码是第一道思维换挡很多教材把循环简单概括成重复执行某段代码这个说法没错但太容易让人误解成循环就是偷懒用的少写几行代码。实际上循环的意义远不止省键盘寿命——它是程序能够处理批量数据、实现复杂算法的基础。后面学数组、字符串、排序、链表全都要靠循环来驱动。如果这一章只是背下了三个循环的语法格式后面会越学越吃力。1.1 从手写重复到让机器重复的思维转变我经常给初学者打一个比方假设要你在黑板上写 1 到 100 的数字你当然可以一个个写写到 100 个也就几分钟的事。但如果要求你写 1 到 100000 呢手写是不现实的你得找到规律用某种规则让这个书写过程自己滚动起来。循环干的就是这件事。在代码里这种规律由三个要素构成从哪里开始初始条件、到什么程度继续循环条件、每次做完之后怎么变迭代更新。你可以把循环想象成一条流水线工件从入口进来检查是否满足加工条件满足就加工一遍加工完推进一格再检查再加工……直到某个时刻条件不再满足流水线停下来。理解了这个模型再看任何一个循环语句都不会慌它本质上就是在描述一条推进—检查—执行—再推进的自动流水线。这也是为什么这一章值得认真停下来思考而不是急着敲代码。脑子里先建立起这个动态模型比多敲十遍语法都管用。1.2 三种循环的性格差异要先有个整体印象C语言提供了三种循环控制结构while、do-while、for。它们在功能上可以互相替代——同一个问题用哪个写都能解但每个都有自己的脾气和最适合的用法。我先给一张整体对照表大家有个印象后面的章节再逐个拆开细讲。循环类型判断时机最少执行次数典型适用场景while先判断后执行0 次循环次数不确定先看条件再说do-while先执行后判断1 次至少要做一次比如菜单选择、输入校验for先判断后执行0 次循环次数明确有清晰的初始化、条件、更新三步从这张表能看出来选择哪种循环核心取决于两个问题你要不要保证至少执行一次以及你事前清不清楚要循环多少次这两个问题想透了三种循环的选用就不是靠死记硬背而是靠需求自然推导出来。1.3 这一章在整个C语言体系里的位置回到系列的整体进度前面几篇讲了数据类型、运算符、输入输出和分支结构那些内容组合起来能写出一条路走到黑或者分岔路口选一条的程序。但真实问题往往没有这么简单——你要统计一组成绩的平均分要找出 100 以内的素数要读取文件里不知道有多少行的数据这些任务没有循环根本无从下手。而且循环结构还会和分支结构频繁嵌套出现比如循环里判断奇数偶数可以说掌握了循环才算把 C 语言三大结构中的最后一个大件装齐了后面学函数、数组、指针都是在这三大结构的基础上盖楼。2. while循环先看门再进门的执行逻辑以及死循环从哪来while是三种循环里最耿直的一个。它的执行逻辑一句话就能说完先判断条件条件为真就执行循环体执行完再回来判断直到条件为假为止。就像进小区门门卫先看你的通行证有证才放你进去没证直接拦在外面一次都别想进。这个一次都不执行的特性是它与do-while最本质的区别。2.1 语法结构和完整的累加求和案例最基本的while写法是while (条件表达式) { // 循环体在条件为真时反复执行的语句 }看一个最经典的入门案例计算 1 加到 100 的和。#include stdio.h int main() { int sum 0; int i 1; // ① 初始化循环变量 while (i 100) { // ② 循环条件 sum i; // ③ 循环体累加 i; // ④ 更新循环变量让循环能前进 } printf(12...100 %d\n, sum); return 0; }这个例子我建议初学者亲手敲三遍不是背而是感受四个部分之间的关系。sum是累加结果的容器必须初始化为 0不然第一次sum i会把垃圾值加进去i是循环变量它决定了循环从哪里开始、到哪里结束i是整段代码的灵魂少了这一行i永远是 1i 100永远为真程序就会陷入死循环。2.2 死循环是怎么产生的以及怎么防说到死循环这是初学者接触while后最先遇到的大事故。程序卡住不动终端疯狂刷输出只能按 CtrlC 强制终止。死循环的根源只有一个循环条件永远为真没有变成假的机会。最常见的两类原因一类是忘记写更新语句就像上面例子去掉i另一类是更新语句永远无法让条件趋于假比如int i 1; while (i 100) { i--; // i 越减越小永远 100 }还有一种隐蔽的死循环是条件表达式本身写得有问题比如把i 100写成i 100同时i从 1 开始递增——1 确实永远不大于等于 100循环体一次都不会执行程序跳过循环结果不是死循环而是空转。所以调试循环时第一件事永远是问自己两句话循环变量初始化了吗每次循环之后循环变量往条件为假的方向走了吗这两句话能解决 90% 的循环入门问题。我在实际调试中的一个小技巧是在循环体开头加一行printf(i %d\n, i);先观察i的变化轨迹。只要轨迹和你预期不符问题立刻暴露等到逻辑稳定了再删掉这行调试输出。别嫌麻烦这比对着代码凭空猜快得多。2.3 while 适合什么场景while最自然的场景是循环次数事先不知道但知道什么时候该停。比如提示用户输入一个正数用户可能输入 1 次就对也可能输入 10 次都不对。这类读到有效输入为止的逻辑用while写起来特别顺因为条件本身就表达了什么时候继续要这个需求int score; printf(请输入一个 0-100 的分数); scanf(%d, score); while (score 0 || score 100) { printf(输入不合法请重新输入); scanf(%d, score); }这段代码把只要输入不合法就一直问翻译得非常直接。等到后面学了文件读写while更是读取未知行数数据的标配——你不知道文件里有多少行只能读到末尾才算结束。3. do-while循环先把事做了再谈条件输入校验场景的最优选do-while和while像一对双胞胎但性格完全相反。它先把循环体执行一遍再去判断条件是否继续。这意味着无论条件一开始是真是假循环体至少会执行一次。还是举进小区的例子do-while是那种不管你有没有证先让你开进去进门之后门卫再查证没证下一趟就不让进了。3.1 语法格式和与 while 的核心差异do { // 循环体 } while (条件表达式); // 注意这里的分号不能丢请注意末尾的分号这是do-while语法上最容易踩的坑漏了分号编译直接报错。和while对比一下执行流程对比项whiledo-while条件判断时机循环体执行前循环体执行后循环体最少执行次数0 次1 次典型前提条件不满足时不该执行无论如何都要先执行一次有时候同一个需求用两种循环都能写但可读性和边界处理难度不一样。比如反复读取合法输入这个问题用while需要在进入循环前先读一次然后在循环末尾再读一次代码里输入语句会重复出现两遍用do-while则只需出现一遍因为第一次读取放进了循环体。3.2 用 do-while 实现易用版用户输入校验回到前面那个请输入 0-100 分数的需求用do-while写出来是int score; do { printf(请输入一个 0-100 的分数); scanf(%d, score); if (score 0 || score 100) { printf(输入不合法请重新输入。\n); } } while (score 0 || score 100);这个版本好在哪里它不需要在读循环前先预读一次也不需要在循环末尾再补读一次输入动作只写在循环体里一次逻辑非常紧凑。这种无论怎样都得先问一遍用户的场景天然就是do-while的主场。类似的应用还有菜单选择先把菜单打印出来读取选项如果选项不在合法范围内就重新打印菜单再读。用户至少有两次机会操作而且程序结构非常直观。3.3 什么时候不建议用 do-whiledo-while也不是万能的。当条件一开始是否为真会直接决定循环体该不该执行时硬用do-while就得在循环体里加if判断来兜底代码反而更绕。比如处理数据时如果数据源本身可能为空一上来就执行一次处理逻辑就不合适这种场景应该用while先判断。简单记至少要做一次是你真正需要的结果时才用 do-while如果能不做就不做更好就用 while 或 for。这个判断标准不是死规则但足够帮助初学者在具体问题里做选择。4. for循环初始化、条件、更新三件套的打包管理for循环是三种循环里用得最频繁、也是初学者最喜欢的一个。原因很直接它把循环所需要的初始化条件判断更新操作三件事全部写在了一行for(...)里结构一目了然不会像while那样容易漏写更新语句。4.1 三个表达式的逐一拆解语法是for (表达式1; 表达式2; 表达式3) { // 循环体 }三个表达式分工明确表达式1初始化循环开始前执行一次。通常用来定义并初始化循环变量比如int i 0。表达式2循环条件每次循环体执行前判断。为真就继续执行循环体为假就退出循环。表达式3更新每次循环体执行完后执行一次。通常用来修改循环变量比如i。它的完整执行顺序是表达式1 → 判断表达式2 → 循环体 → 表达式3 → 再判断表达式2 → ……你注意判断总是发生在循环体之前所以for和while一样循环体最少执行 0 次。用for重写前面的 1 到 100 累加求和int sum 0; for (int i 1; i 100; i) { sum i; } printf(sum %d\n, sum);相比while版本初始化int i 1和更新i都被收纳进了for的头部循环体里只剩纯逻辑sum i阅读时一眼就能看出这个循环从 1 开始到 100 结束每次加 1。4.2 for 和 while 的等价转换看懂一个就懂两个很多人刚学时有个疑问既然for这么方便还要while干嘛实际上两者完全可以互相转换// for 版 for (int i 1; i 100; i) { sum i; } // 等价的 while 版 int i 1; while (i 100) { sum i; i; }看出对应关系了吗for的表达式1就是while前的初始化表达式2就是while的条件表达式3就是while循环体末尾的更新。所以for并没有发明新东西它只是把这三件事打包放在了一行强迫你把它们写清楚减少遗漏。这也是为什么我建议初学者优先从 for 入手因为它的结构化设计天然引导你把循环的三个关键要素一次写齐。4.3 省略写法、逗号表达式和常见的高级用法for的三个表达式都可以省略但两个分号必须保留。比如int i 1; for (; i 100; i) { // 省略初始化因为 i 在外面已经定义好了 sum i; }甚至可以把更新语句放进循环体写成for (;;)——这就是一个经典的隐形死循环写法配合break使用下一节会讲。但要提醒大家省略写法虽然语法合法可读性往往变差入门阶段不建议刻意追求写得短代码首先是写给人的其次才是给机器跑的。逗号表达式也常出现在for头部一次处理多个变量for (int i 0, j 10; i j; i, j--) { printf(i %d, j %d\n, i, j); }这个循环同时用两个变量从两端往中间逼近在很多算法题里都会碰到。记住一个原则for头部里放和循环推进相关的操作别把业务逻辑塞进去否则代码会变得非常难读。5. break和continue一个负责急刹车一个负责跳台阶学完三种循环接下来就是循环控制结构里的两大辅助开关break和continue。它们不创建新的循环而是改变循环的执行流。很多初学者把这两个搞混其实区分起来特别简单break是整个循环直接结束急刹车continue是只跳过本次循环的剩余部分、进入下一次迭代跳台阶。5.1 break立即跳出当前循环break在循环体中出现时程序会立刻终止整个当前循环跳到循环后面的第一句代码继续执行。典型的应用场景是提前结束循环明明还没跑完但目标已经达成了就没必要继续空转。比如判断某个数是不是素数不需要真的把 2 到 n-1 全部试一遍只要找到一个能整除的因子就可以立即下结论int n 37; int isPrime 1; // 假设是素数 for (int i 2; i n; i) { if (n % i 0) { isPrime 0; break; // 找到因子立刻退出循环 } } if (isPrime) { printf(%d 是素数\n, n); } else { printf(%d 不是素数\n, n); }这个例子里的break价值非常明显如果 n 很大你提前在第 2 步就找到了因子后面成百上千次判断全都可以省掉。5.2 continue跳过本次循环的剩余部分但循环要继续continue则温和得多。它遇到后直接跳到本次循环体的末尾不执行循环体里continue后面的语句但会照常进入下一次迭代先执行for的表达式3再判断循环条件。一个典型场景是过滤输出 1 到 20 之间所有不能被 3 整除的数遇到能被 3 整除的就跳过不输出它for (int i 1; i 20; i) { if (i % 3 0) { continue; // 跳过本次输出 } printf(%d , i); }执行结果会是1 2 4 5 7 8 10 11 13 14 16 17 19 20。continue在这里相当于流水线上的剔除不合格品不合格的直接推走但整条流水线照常运转。这和break的整个车间停工是完全不同的逻辑。5.3 嵌套循环里的 break 和 continue只对最近的一层生效这是初学者最容易搞蒙的地方。break和continue都只作用于它们所在的那一层循环不会跳出外层。比如for (int i 1; i 3; i) { for (int j 1; j 3; j) { if (j 2) { break; // 只会跳出内层 for外层 i 的循环不受影响 } printf(i%d, j%d\n, i, j); } }这段代码会输出三行i1, j1、i2, j1、i3, j1。每次内层循环到j2就中断重新回到外层。很多人在这里以为break会把内外两层一起干掉结果 debug 半天发现外层照常跑。如果你确实需要跳出多层循环传统做法是使用一个标志变量在内外层同时判断更后面也可以学goto但入门阶段不建议优先用goto先学会标志变量法更稳妥int found 0; for (int i 1; i 3 !found; i) { for (int j 1; j 3; j) { if (j 2) { found 1; break; } } }把标志变量写进外层循环的条件里break内层退出后外层条件也变成假循环就自然结束了。6. 嵌套循环九九乘法表背后的两层思维循环里面再套循环就是嵌套循环。这是第一次让程序的维度从一维变成二维的地方也是很多初学者的劝退点。其实嵌套循环一点都不玄乎核心就一句话**外层循环每执行一次内层循环就完整走一遍。**把握住这个执行顺序嵌套循环就不再可怕。6.1 从九九乘法表看嵌套循环的规律九九乘法表是理解嵌套循环最经典的入门案例。先确定规则一共 9 行第 i 行有 i 个式子每个式子是列号*行号乘积的格式。用代码实现for (int i 1; i 9; i) { // 外层循环控制行 for (int j 1; j i; j) { // 内层循环控制列列号不超过行号 printf(%d*%d%-2d , j, i, i * j); } printf(\n); // 每行结束后换行 }运行效果就是标准的左下三角九九乘法表。拆解一下执行流程当外层i 1时内层j从 1 循环到 1输出 1 个式子后换行当外层i 2时内层j从 1 循环到 2输出 2 个式子后换行……这样一层一层推进9 行全部打印完毕。我在黑板上讲这个例子时会在纸上一行一行模拟执行把i、j和输出的变化全部列出来跑完 3 行基本就通了。6.2 二维遍历的思维顺序外层管行、内层管列九九乘法表只是特例嵌套循环更普遍的应用是处理二维数据比如一个 3 行 4 列的矩阵要把每个元素都遍历一遍。这时候外层循环负责行内层循环负责列是绝大多数二维遍历的固定套路int rows 3, cols 4; for (int i 0; i rows; i) { // 外层每一行 for (int j 0; j cols; j) { // 内层该行的每一列 printf((%d,%d) , i, j); } printf(\n); }你可以在纸上把输出模拟一遍会发现它严格按照第 0 行的所有列、第 1 行的所有列、第 2 行的所有列这个顺序推进。理解了这个顺序不管是处理图像像素、二维数组还是后面学的矩阵运算你的脑子都会有清晰的轮廓。6.3 顺手聊聊嵌套循环的性能意识嵌套循环看起来简单但隐藏着一个性能问题循环层数越多执行次数增长越恐怖。假设外层 100 次、内层 100 次循环体就要执行 10000 次如果再来一层就是 100 万次。入门阶段写个小程序感觉不出来但等以后处理真实数据、甚至刷算法题时这种循环爆炸直接关系到程序能不能在合理时间内跑完。所以从学嵌套循环开始就要养成估算循环次数的习惯看到for (i 0; i n; i) for (j 0; j n; j)能脱口而出这是 $O(n^2)$ 的操作。不求现在就算复杂度但心里要有个这个循环可能要跑多久的尺子。6.4 一个小综合练习输出质数表把前面所有知识串起来我自己带学生时最喜欢布置的一个小练习是输出 2 到 50 之间的所有质数每行 5 个。这个题目看起来简单其实要用到嵌套循环外层遍历数字内层判断是否为质数、break找到一个因子就提前退出、计数器控制换行。整体写法参考#include stdio.h int main() { int count 0; for (int num 2; num 50; num) { int isPrime 1; for (int i 2; i num; i) { if (num % i 0) { isPrime 0; break; } } if (isPrime) { printf(%-4d, num); count; if (count % 5 0) { printf(\n); } } } return 0; }练这个题的目的不是炫技而是让循环思维真正落地你能把一个数一个数地检查转化为两层循环的自动推进说明你已经从我手动做一遍过渡到了我写规则让机器做一遍这一章的思维换挡就算真正完成了。7. 循环实战里最容易翻车的地方我帮你排过雷循环的语法本身不难但实际写起来有几个错误几乎每个初学者都会遇到而且它们往往编译能过、也不会立刻报错只在运行时给你一个奇怪的结果。这一节我集中排一下最常见的雷顺便把调试思路一起给出来。7.1 分号、空循环体、更新遗漏三个看不见的错误第一个雷while 后面多写一个分号循环体变成空语句。比如int i 1; while (i 100); { sum i; i; }这里的while (i 100);因为分号结束循环体是那条空语句而下面的大括号块跟循环完全没关系只会执行一遍。同时i永远不会更新程序直接死循环。这种错特别隐蔽因为编译不会报错肉眼不仔细看也容易忽略。排查方法很简单看到while或for那一行结尾心里默念一下分号到底属于谁。第二个雷循环体不小心为空或者只执行一次。比如for后面紧跟一个分号for (int i 1; i 10; i); printf(%d, i);你以为会输出 1 到 10实际for自己空转 10 遍然后printf只执行一次。这些都是分号放错位置惹的祸。第三个雷忘记更新循环变量。前面提过while里容易漏写ifor里相对不容易漏因为更新被固定写在了头部。这也是我推荐入门阶段多用for的原因之一——它的语法结构天然地在提醒你初始化、条件、更新三件事一个都不能少。7.2 循环变量的作用域与生命周期for 循环头的 i 到底能不能在外面用很多初学者会遇到这个困惑在for循环头里定义了int i循环结束后在循环外面想再打印i的值结果一编译就报错未定义标识符 i。原因在于C99 标准之后在for的初始化表达式里定义的变量其作用域仅限于这个 for 循环循环一结束i就消亡了。需要在循环外面使用循环变量的最终值时就必须在循环外先定义int i; for (i 1; i 10; i) { // ... } printf(循环结束后 i %d\n, i); // i 仍然可以使用这个细节看起来小但涉及变量的作用域和生命周期是后续学函数、指针时反复要用的基础概念建议从现在就建立清晰认识。7.3 调试循环的三种实用手段遇到循环行为不符合预期别干瞪眼按我的习惯依次用下面三招第一招打印中间值。在循环体里加printf把关键变量每一轮的值打出来。这是最朴素也最有效的办法。我曾经带过一个学生累加求和结果总是不对加了打印才发现他每次循环都忘了把sum重新清零导致多组数据累加时互相污染。第二招缩小数据规模。如果你要处理 1 到 1000 的数据先改成 1 到 5 跑一遍把执行轨迹在纸上写出来和代码逻辑对一遍。数据一大眼就花了规模一小规律立刻清晰。第三招条件断点。用 IDE 的调试器在循环体里设断点并且设置条件比如i 50时才停下来。这样不用循环到几百次断点才拦一次能精准定位到出问题的那一轮。入门阶段学一下这个后面排错效率能提升很多。这三招没有高深技术但非常管用。循环报错不丢人丢人的是看到结果不对却只会重启程序那永远找不到问题在哪。写到这儿关于循环控制结构该讲的核心内容基本都覆盖了。我最后再啰嗦一句个人体会循环这一章真不用急着求快拿一张纸把while、do-while、for各写两三道题手动模拟执行画一画变量变化表比盯着屏幕看十遍视频都管用。等循环真正长在脑子里了后面学数组、指针、函数你会发现很多难点其实都是在循环这一关埋下的伏笔而现在地基已经打好了。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →