
1. 为什么分支和循环是C语言的地基说到C语言分支和循环是任何人都绕不过去的两条腿。程序说白了就是一系列指令但真正的程序不是从头到尾一条道走到黑而是要根据条件决定走哪条路根据条件决定要不要重复执行某段代码。分支负责“选择”循环负责“重复”这两样东西组合起来才能写出有逻辑的程序。C语言里几乎所有的算法、业务逻辑、底层驱动最终都能拆解成一组组判断和循环。对刚入门的朋友来说学完语法不算完能把分支和循环用顺了才算是真正迈进了编程的大门。这篇文章面向三类人一是刚学C语言的大学生课堂上听懂了但不会写题二是自学转行的朋友需要一份能照着练的实战笔记三是想复习基础、准备笔试面试的兄弟。看完之后你至少能独立写出菜单选择、猜数字、九九乘法表这类经典程序也能看懂复杂代码里最常见的if、switch、for、while嵌套逻辑。1.1 程序不是流水账而是岔路口和循环道我当年教第一门编程课的时候很多学生最困惑的问题是明明我把每个语句都看懂了为什么整段程序跑起来的结果就懵了原因就在于他们始终在用“从上到下读课文”的方式来看代码而没有建立“控制流”的概念。控制流就是程序执行的“路线图”。顺序结构是直线分支结构是岔路口循环结构是兜圈子的环形路。C语言的三种基本结构里顺序最简单分支和循环才是真正决定程序复杂度的部分。你可以把分支理解成你每天出门前的决策如果下雨就带伞否则不带。循环则像健身时的“每组做10个做3组”条件满足就反复执行。程序里所有动态行为都是这两种结构拼出来的。理解这个之后你再看任何代码就不慌了先找哪里有判断再找哪里有循环然后看它们怎么嵌套和衔接。这个“找控制流”的习惯比背一百个语法点都管用。1.2 学完你能做到什么分支和循环的用途有多广远的不说你写一个登录功能用户名密码对不对这是分支你写一个数据校验要逐条检查一整个数组这是循环你做单片机开发按键扫描、LED闪烁延时本质也是循环加分支。C语言号称“什么都能写”靠的就是这两板斧。练习的目标可以定得很具体能用if-else实现多条件判断能用switch处理多路分支能用for、while、do-while实现各种重复操作能使用break、continue控制循环节奏能看懂两层甚至三层嵌套。达到这个程度C语言的“地基”就算是打牢了。2. 分支语句if、if-else、switch的底层逻辑与用法分支语句解决的是“在多个可能中选一个”的问题。C语言里最核心的分支工具就是if系列和switch别看语法简单里面的细节足以让新手栽好几个跟头。2.1 if系列从最简单判断到嵌套陷阱先看最基本的ifif (score 60) printf(及格\n);这里有一个很多人第一次写就犯的错if后面括号里的条件不管多复杂最终都会被当成“真”或“假”。C语言没有专门的布尔类型规则是0为假非0为真。所以if (score - 60)这种写法是完全合法的只要score不等于60表达式结果非0条件就成立。新手容易把条件想成“必须是比较表达式”其实任意数值都可以。多条件判断一般用if-else if-elseif (score 90) { printf(优秀\n); } else if (score 80) { printf(良好\n); } else if (score 60) { printf(及格\n); } else { printf(不及格\n); }注意这里的顺序很重要。程序从上往下匹配一旦某个条件成立后面的else if就不再执行。所以如果你把score 60放在最前面那所有及格以上的分数都会直接走到“及格”后面的分支就永远不会被触发。这就是分支顺序的坑笔试里特别爱考。嵌套if也要小心花括号的匹配。我见过无数次这样的代码if (a 0) if (b 0) printf(两个都为正\n); else printf(这个else到底配对谁\n);这个else看起来跟第一个if对齐但实际上C语言规定else总是跟它最近的未匹配的if配对。所以这里的else匹配的是第二个if (b 0)而不是第一个。要避免这个问题最好的办法是永远用花括号把每个分支体括起来哪怕只有一条语句。别偷懒偷懒的代价是逻辑莫名其妙出错。2.2 switch-case多路分支的正确姿势与穿透问题当判断条件特别多的时候if-else写一长串很累switch更合适switch (month) { case 1: case 2: case 3: printf(春季\n); break; case 4: case 5: case 6: printf(夏季\n); break; default: printf(输入月份有误\n); break; }switch后面的表达式必须是整数类型int、char等不能是浮点数也不能是字符串。case后面必须是整型常量表达式不能写变量。这个限制很多人一开始不知道结果在switch里写case i:直接编译报错。switch最经典的问题是“穿透”。如果一个case分支里没有break程序会继续执行下一个case的代码直到遇到break或整个switch结束。这个特性有时候是故意用来合并分支的比如上面例子中连续三个case共享一个输出。但更多时候是新手忘写break导致结果完全不对。我个人的建议是就算某个case分支只有一行代码也顺手加上break除非你明确知道自己就是要利用穿透特性。因为你在写的时候很清楚但过两个星期回来看你大概率会骂自己当初为什么偷懒。switch和if-else怎么选如果条件是“某个整型变量的值等于几个固定值”用switch如果是“范围判断、大小比较、多个条件组合”用if-else。用对了工具代码读起来很舒服。2.3 分支实战成绩等级划分我们结合上面讲的写一个完整的小程序输入一个百分制成绩输出对应的等级。A为90分以上B为80到89C为70到79D为60到69E为60分以下。可以顺便校验输入是否在0到100之间。#include stdio.h int main(void) { 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; }这里为什么要先判断非法输入因为如果用户输入了负数或超过100的数后面的判断会得出非常荒谬的等级。写分支的时候先把异常情况拦截掉再处理正常情况这个顺序也是职业习惯。很多程序漏洞就是少了这种前置判断。用switch也能实现同样的功能但需要用score除以10来把连续范围映射成整数switch (score / 10) { case 10: case 9: printf(等级 A\n); break; case 8: printf(等级 B\n); break; case 7: printf(等级 C\n); break; case 6: printf(等级 D\n); break; default: printf(等级 E\n); break; }注意score / 10是整数除法结果直接去掉小数部分这是整数运算的特点。这个映射思路在C语言里特别常见值得记一下。3. 循环语句while、do-while、for的取舍与细节循环的三种写法各有脾气。很多新手觉得“反正都能用随便选一个”但真正写起来选错循环结构会让代码绕来绕去自己都看不明白。3.1 while与do-while先判断还是先执行while循环的格式是“先判断再执行”。条件不成立时循环体一次都不会执行int i 0; while (i 10) { printf(%d , i); i; }这个代码会输出0到9。这里有个新手常犯的错误忘记在循环体里修改计数变量导致条件永远为真程序死循环。写while循环的时候一定要问自己一个问题这个循环靠什么变量来让条件最终变成假如果没有这个循环就是一颗计时炸弹。do-while则相反它是“先执行再判断”。因此循环体至少会跑一次int num; do { printf(请输入一个正数); scanf(%d, num); } while (num 0);这个例子里程序至少会先要求输入一次然后判断输入是否合法。输入0或负数就继续问直到输入正数为止。凡是“不管怎样都要做一次然后再看要不要继续”的场景do-while就是最合适的。典型的应用是菜单程序菜单至少显示一次用户选退出才结束。但do-while有个坑在于很多人忘了末尾的分号。while (condition);这个分号是语法的一部分漏了直接编译报错。而普通while循环如果多加了这个分号会变成空循环体死在原地。这类“差之毫厘”的细节永远是调试题的最爱。3.2 for循环计数循环的黄金标准for循环的完整形式是三个表达式加一个循环体for (初始化; 条件; 步进) { 循环体; }用求1到100累加和的例子最直观int sum 0; for (int i 1; i 100; i) { sum i; } printf(sum %d\n, sum);int i 1是初始化i 100是循环继续的条件i是每轮结束后的步进。三个部分都可以省略两个分号不能省。省略条件就等价于条件永远为真写成for (;;)就是死循环。这种写法在服务器代码里偶尔能看到配合break使用。for循环最适合“已知要循环多少次”的场景。它的优势在于把初始化、条件、步进都集中在头部看代码时扫一眼就明白循环的边界。我个人的习惯是只要循环次数能用一个变量或者常量表达清楚就用for如果循环次数不确定取决于某个条件的实时变化就用while。一个容易误解的地方是for (int i 0; i 10; i)里面的i在循环结束后还能不能用这取决于C标准。C99之后声明在for初始化部分的变量作用域仅限于循环体内循环一结束变量就失效了。所以不要在循环外面再造访它老老实实在外面定义。有些老编译器用C89标准不支持在for里面声明变量这也是很多教材还在写int i; for (i 0; ...)的原因。3.3 循环嵌套与break、continue、goto循环跟循环可以套着用最常见的例子就是打印九九乘法表for (int i 1; i 9; i) { for (int j 1; j i; j) { printf(%d*%d%d\t, j, i, j * i); } printf(\n); }外层循环控制行内层循环控制列内层j小于等于外层i保证只打印左下三角。这种嵌套逻辑一开始不太容易转过弯我的建议是拿笔在纸上画一个9乘9的格子标出哪些格子要打印然后看代码里j的取值范围是怎么圈出这些格子的。画完一遍嵌套循环基本就通了。break是跳出当前这一层循环continue是跳过本次循环的剩余部分、直接进入下一次。注意break只能跳出一层如果两层循环里想直接跳出外层break做不到。这时候可以用goto虽然很多教材劝退goto但跳出多层嵌套用goto确实是最直白的方案for (int i 0; i 10; i) { for (int j 0; j 10; j) { if (arr[i][j] target) { goto found; } } } printf(没找到\n); goto done; found: printf(找到了i%d, j%d\n, i, j); done: return 0;这里用goto把双层循环的提前结束写得非常干净。我的态度是生产代码中尽量少用goto但这并不代表要完全禁用。在C这种老派语言里错误处理、多层退出场景goto依旧是一种合法的武器。你只要保证跳转方向清晰别跳进别人的代码块里就行。4. 组合实战用分支和循环写一个猜数字游戏光说不练假把式。这部分我们写一个完整的猜数字游戏把分支和循环全部串起来。需求很简单程序先随机生成一个1到100的整数用户输入数字猜程序提示“大了”、“小了”猜中结束并统计次数。4.1 需求拆解与流程设计写代码之前先想清楚流程生成随机数清空猜的次数进入循环提示用户输入比较输入值与目标值大、小、相等大于目标值提示“太大了”小于提示“太小了”相等结束循环统计输入次数并在结束时显示这里输入可能非法吗有可能。如果用户输入了一个不在1到100之间的数字甚至输入了非数字字符程序也要处理。不过为了控制篇幅我们先用一个合理输入为前提实现非法输入的话题放到后面的常见问题里聊。这个设计里循环条件是“没猜中”循环体内根据比较结果去更新状态正好同时考验你对while和if的理解。4.2 代码实现与逐行讲解#include stdio.h #include stdlib.h #include time.h int main(void) { int target, guess, count 0; srand((unsigned)time(NULL)); target rand() % 100 1; printf(我已经想好了一个1到100之间的数字开始猜吧\n); while (1) { printf(输入你的猜测); scanf(%d, guess); count; if (guess target) { printf(太大了\n); } else if (guess target) { printf(太小了\n); } else { printf(猜中了你一共猜了%d次。\n, count); break; } } return 0; }逐行解释几个关键点。srand((unsigned)time(NULL))是把当前时间作为随机种子rand()生成一个伪随机数% 100 1把结果映射到1到100。如果不调用srand每次运行程序生成的随机数序列都一样游戏就没意思了。主循环用的是while (1)这看起来像死循环但内部有一个break在猜中时跳出。这种“循环条件写成永远为真靠break退出”的模式在交互式程序里非常常见。优点是逻辑集中在循环体内部不容易把退出条件写乱缺点是一旦忘记写break程序就真的死循环了。所以用这个模式要格外自律。count写在每次输入之后保证猜中那一轮也会被统计。你要是把它写在else分支里统计次数就会少1。这种细节肉眼检查很难看出来调试的时候才暴露。4.3 扩展思考猜数字还能怎么改这个程序的可扩展性极强。你可以限制最多猜10次猜不中就结束需要在外层加一个次数判断你可以统计用户猜了超过100轮的情况说明用户缺乏策略你还可以把范围改成1到1000提升难度也可以用do-while改写让初体验更平滑。动手改一次比看十遍代码都有效。我还建议你做一个小改动如果用户输入的guess不在1到100之间提示他输入不合法。这个需求看着简单但它会逼着你考虑分支的优先级。因为输入不合法时既不能提示“太大了”也不能提示“太小了”必须先把非法情况过滤掉。这是非常贴近生产环境的思维训练。5. 常见错误与排查技巧实录下面这些错误每一个都是我和学生们真真切切踩过的坑。我把它们集中在一起做成一个速查表方便你在调试时对照。5.1 最常见的语法与逻辑错误清单错误类型错误代码示例问题解释正确写法赋值当判断if (x 0)把0赋值给x表达式结果为0条件总是假if (x 0)分号问题while (i 10); { ... }分号让循环体变成空语句后面的代码块永远不执行去掉分号变量未初始化int sum; for (...) sum sum i;sum初始值是垃圾值累加结果完全错误int sum 0;break漏写switch分支没有break程序穿透到下一个分支结果错乱每个case结尾加break边界条件少算for (i 0; i n; i)写成了i n-1少处理最后一项想清楚区间是左闭右开这里重点说第一个。if (x 0)这种写法在C语言里不会编译报错因为赋值表达式本身有返回值。很多编译器会弹警告但新手往往忽略警告。这就是为什么所有老师都反复强调要用判断相等却依然有人前赴后继地写错。我的建议是如果你判断的是常量可以写成if (0 x)万一少写一个等号0 x这种代码编译都过不去。这个“Yoda条件”技巧虽然看着别扭但能保命。5.2 调试技巧打印、单步、注释排查遇到逻辑跑不对第一步不是瞪着眼睛看代码而是加printf。我调试分支循环程序最常见的做法是在关键判断处打印条件和变量值。printf(i%d, j%d, sum%d\n, i, j, sum);比如检查循环边界你可以把每次循环的i值打出来跟纸面上算好的预期对比。一旦发现i多跑了一圈或者少跑了一圈问题就定位到了。用printf调试虽然土但特别有效。等到经验丰富了再用调试器做单步跟踪。如果你用的是VS、CLion、VSCode配GDB都可以打断点逐行看变量的变化。单步执行特别适合排查分支嵌套和循环嵌套因为你能清清楚楚看到程序到底走了哪条路而不是靠猜。还有一个古老但好用的方法注释排查法。当你怀疑某段代码有问题先把可疑的语句注释掉再跑一遍看行为变化。如果程序恢复正常说明问题就在刚才注释掉的代码里。这个过程可以二分进行很快就能缩小范围。5.3 避坑经验等号、分号、边界、变量初始化我把这些坑串成一个“检查四部曲”。写完分支循环代码后别急着运行按顺序自查一遍。第一部查等号。所有if和while的条件里凡是比较相等的地方确认用的是不是。赋值操作只应该出现在你确实想要赋值的语句中。第二部查分号。do-while末尾的分号有没有写for头部的两个分号有没有丢掉while、if后面有没有鬼使神差多出分号分号这种东西多一个少一个程序面目全非。第三部查边界。循环里从0开始还是从1开始结束条件是 n还是 n数组下标访问绝不能越界分支判断用大于还是大于等于都要跟你的设计稿对齐。我常提醒学生代码里的每一个数字都要能说出它的来历。第四部查初始化。所有累加器、计数器、标志变量在进入循环之前是否已经初始化过。你用了C语言未初始化的局部变量它的初始值是不确定的也许本地运行偶尔正确换个环境就炸了。这种随机性误差最可怕。按照这四步走一遍能消灭掉60%以上的低级错误。剩下的逻辑错误交给打印和单步调试。最后再分享一个实操中的个人习惯我写循环的时候会习惯性地在循环体结束的位置加一行注释写明“这里一轮结束i后进入下一轮”。这个注释看着多余但当你需要修改三个月前的代码时它能帮你快速恢复上下文。C语言里的注释不会影响性能但能救你的命。分支和循环是基础中的基础但越是基础的东西越值得静下心来反复打磨。把上面的例子全部自己敲一遍改一改、跑一跑比看十篇教程都有用。