
1. 为什么要死磕“分支和循环”C语言学到第三讲大部分人已经能把“输入、计算、输出”跑通了也就是从上到下把代码写一遍运行一下结果出来。但你会发现这种能力离“写程序”还很远——因为真实的程序从来不是一条直线往下走的。举个例子你写一个“判断成绩等级”的小工具90分以上是A80到89是B70到79是C60到69是D60以下是不及格。如果你只会顺序执行这个需求基本没法做。你得根据不同的分数走不同的逻辑这就是“分支”。再比如你要打印1到100的和总不能写100次加法吧。你得让同一段代码反复执行这就是“循环”。说句实话C语言里真正决定一个人基础扎不扎实的不是指针也不是内存管理恰恰就是这两个东西分支和循环。指针没学好你写不了复杂代码。但分支循环没学好你连复杂代码的门都摸不到。很多人在学指针的时候一脸懵回头一看其实是前面的分支逻辑和循环边界都没理清楚。这一讲只聊“上”也就是分支和循环的最核心部分if / else、switch、while、for。不整花活不搞嵌套地狱就把最常用的写法和最容易踩的坑讲透。适合刚学完顺序结构、准备进阶的初学者也适合那些写了一年C语言但老觉得代码不够清爽的人——很多时候回头补基础比学新东西更有用。2. 分支语句让程序学会“看情况办事”2.1 if / else 的基本姿态C语言里最常用的分支手段就是if。它的语法结构几乎是个人都能看懂if (条件) { // 条件为真时执行 } else { // 条件为假时执行 }但越是简单的东西越容易在细节上翻车。我见过太多新手写出这样的代码if (a 0) printf(正数); else printf(非正数);这段代码语法没错也能跑。但如果你后面加代码很容易加错位置if (a 0) printf(正数); printf(我要打印两行); // 这行和 if 没关系 else printf(非正数);这直接编译不过因为else前面只有一个语句受if控制printf(我要打印两行)夹在中间根本找不到配对。就算你给它换行、缩进也只是让错误更隐蔽。所以我一直建议无论if、else、while还是for只要后面有执行体一律加大括号。哪怕里面只有一行代码大括号也写着。这不是强迫症是为了让你三个月后回来看代码时不会因为缩进混乱而搞不清逻辑。这个习惯越早养成越好到了企业里写多分支业务逻辑一个不小心就会埋雷。2.2 else if 是代码里的“分诊台”现实问题很少是非黑即白。成绩分ABCD交通灯分红黄绿菜单分1234。这时候你要写多个分支。新手第一反应是这么写if (score 90) printf(A); if (score 80) printf(B); if (score 70) printf(C); if (score 60) printf(D); if (score 60) printf(不及格);跑起来好像没问题。但你想想如果score是95第一个if就把A打印出来了后面的if还会继续判断等于白白浪费了四次比较。更麻烦的是一旦每个if里不只是printf而是几十行代码程序就会同时执行多个分支结果完全错乱。正确写法是把它们串起来if (score 90) { printf(A); } else if (score 80) { printf(B); } else if (score 70) { printf(C); } else if (score 60) { printf(D); } else { printf(不及格); }注意这里的顺序非常关键90的判断必须在前然后逐渐往下放低标准。你把顺序写成else if (score 60)在前那60分的人会先走进D后面全被跳过。分支顺序就是逻辑覆盖范围的大小区间必须先小后大先窄后宽。这个用“分诊台”类比最好理解——先看最急的重症再看普通号。2.3 switch 的打开方式当你的分支判断是基于“某个变量的值等于多少”时switch通常比一堆else if更清爽。switch (day) { case 1: printf(周一); break; case 2: printf(周二); break; case 3: printf(周三); break; default: printf(未知); break; }switch的核心机制是“跳转”算出day的值后直接跳到对应case开始执行。如果你不在每个case末尾写break程序不会自己停在case边界而会继续往下穿——这叫“贯穿”fall-through。有些时候贯穿是故意用的。比如你要统计“元音字母数量”可以把a、e、i、o、u放到同一个分支里switch (ch) { case a: case e: case i: case o: case u: count; break; default: break; }这是常规操作没问题。但是对那些“顺手忘掉break”的新手来说case贯穿就成了最常见的逻辑陷阱。一个原则如果你没打算贯穿case后面必须写break。一旦哪天你发现某个case多走了几步先检查break是不是漏了。另外要说一个细节switch里的case后面必须跟着常量表达式不能写case score 60:这种范围判断。C语言的switch天生不适合做范围判断想做范围和多个条件的组合老老实实用else if。别拿switch硬撑场景工具选错了后面全是坑。2.4 三目运算符分支的“速写版”如果你只是想根据条件给变量赋个值用三目运算符会非常省事max (a b) ? a : b;这行的意思是如果a大于bmax等于a否则等于b。三目运算符的优点是表达紧凑缺点是嵌套多了根本没法读。我见过有人写出这样的代码max (a b) ? ((a c) ? a : c) : ((b c) ? b : c);三四个三目嵌套在一起说句难听的写的人过两天自己都看不懂了。你的代码是给人看的不是拿来炫技的。三目运算符只适合单层、简单的赋值场景。一旦发现需要嵌套立刻换成if去写逻辑清晰比代码短重要得多。3. 循环语句让程序“转起来”3.1 while先想条件再执行while是最直观的循环先判断条件条件成立就执行循环体然后回来继续判断。只要条件永远成立它就一直循环。所以while特别适合处理“不知道要循环多少次但知道什么时候该停”的场景。int n 12345; int count 0; while (n 0) { n / 10; count; } printf(%d, count);比如上面这段统计整数n的位数每次除以10就少一位直到变成0。你不需要提前知道12345有几位循环自己会停。这就是while的典型用法。但while有个著名的问题如果条件一开始就不满足循环体一次都不会执行。这不算bug但有时候会成为隐患。比如int n 0; while (n 0) { // 这段根本不会执行 }如果你是先想着“把这段逻辑循环执行到最后”后来发现边界值恰好让条件一开始就不成立那你的处理逻辑就被跳过了。所以用while前务必先问自己条件一开始为假时这段代码应不应该执行如果应该那就别用while考虑do-while。3.2 do-while无论如何先做一次do-while和while的唯一区别是do-while先执行一次循环体再判断条件。这就保证循环体至少会执行一次。适合的场景很典型——用户交互验证。比如菜单选择你得先让用户输入一次然后判断输入对不对不对再重来do { printf(请输入1到4之间的数字); scanf(%d, choice); } while (choice 1 || choice 4);如果换成while你就得在循环前面先写一次scanf然后在循环里面再写一次scanf让人感觉重复。do-while直接抹掉了这个别扭。记住这个判断标准这段逻辑无论条件是否成立至少应该执行一次吗是就用do-while不是就用while。3.3 for循环把“初始化、条件、步进”写在一行三个循环里for是使用率最高的。它的语法结构是for (初始化; 条件; 步进) { // 循环体 }比如打印1到10for (int i 1; i 10; i) { printf(%d\n, i); }for循环的好处在于把循环的三个关键要素集中到了一行让人一眼就看清楚循环从哪里开始、到哪里结束、每次怎么变化。而while则要把这三个要素拆开放在不同位置读起来相对费劲。很多新手不理解为什么for比while“高级”其实不是高级是for更不容易忘东西。三个要素放在一起你写的时候自然知道该初始化、该写条件、该写步进。而while你只写了while (i 10)经常忘记在循环体里写i于是死循环就来了。在条件判断上我建议用i n而不是i n。不是绝对正确但i n的区间是半开区间[0, n)在很多算法、容器遍历的场景里这个写法能直接避免越界问题也方便和数组下标对齐数组下标就是0到n-1。初学者养成for (int i 0; i n; i)的惯性写法后面接触数组和指针时会舒服很多。3.4 break 和 continue 的正确使用姿势break在switch里是用来跳出分支的在循环里则是用来“提前终止循环”。continue和break不同continue不是退出循环而是跳过本次循环剩余语句直接进入下一次判断。举个例子打印1到10之间所有偶数。for (int i 1; i 10; i) { if (i % 2 ! 0) { continue; // 奇数直接跳过不打印 } printf(%d\n, i); }而break是找到某个目标后就停。比如从一个数组里找第一个大于100的数。for (int i 0; i n; i) { if (arr[i] 100) { printf(找到%d, arr[i]); break; } }这里有几个地方需要特别注意。continue在for循环里会先执行步进再判断条件所以使用for循环时continue没什么大问题。但如果你用while循环continue容易造成死循环。看一下这段int i 0; while (i 10) { if (i 5) { continue; // 死循环没有执行i } i; }当i等于5时continue跳过了下面的i然后回到while条件判断i还是5于是又进入循环又continue永远卡死。这种问题在初学者里非常常见排查起来也特别费劲。我的建议是能用一个变量统一迭代逻辑的优先用for循环。除非场景特殊否则whilecontinue的组合等于给自己挖坑。4. 几个必须能独立写出来的经典场景分支和循环学得怎么样不是看背了多少语法而是看能不能拿它解决实际问题。下面这三个场景是C语言里极其经典的入门题也是很多面试题的雏形。我建议你打开编译器每道题都亲手敲一遍。4.1 判断闰年闰年的规则是能被4整除但不能被100整除的年份是闰年能被400整除的年份也是闰年。用代码写#include stdio.h int main() { int year; 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和逻辑运算掌握得不错。很多初学者在这里会犯一个错把year % 100 ! 0漏掉直接写成year % 4 0 || year % 400 0。那结果就是1900年也会被当成闰年因为1900能被4整除。可实际上1900不是闰年少了最关键的限制。4.2 统计一个数字中某数字出现的次数比如输入一个长整数n统计其中数字7出现的次数。这种题是while循环的标准练习核心思想是用取模和整除把每一位拆出来#include stdio.h int main() { long long n; int target 7; int count 0; scanf(%lld, n); if (n 0) { count (0 target) ? 1 : 0; } else { while (n ! 0) { if (n % 10 target) { count; } n / 10; } } printf(数字7出现%d次\n, count); return 0; }这里给n加了if (n 0)的判断是因为数字0的个位是0但while循环在n0时一次都不进直接输出0次逻辑不对。这正好呼应前面说的while条件一开始为假时循环体不会执行你必须要考虑这种情况是否被允许。处理数字位数的题目是while循环的灵魂场景刷几道就理解透了。4.3 猜数字小游戏这个题目实在经典通过生成一个随机数让用户反复猜大了提示大小了提示小直到猜中。代码里同时用到了循环和分支还有break控制退出。#include stdio.h #include stdlib.h #include time.h int main() { int answer, guess; srand(time(NULL)); answer rand() % 100 1; while (1) { printf(请输入你猜的数字(1-100)); scanf(%d, guess); if (guess answer) { printf(大了\n); } else if (guess answer) { printf(小了\n); } else { printf(恭喜猜对了\n); break; } } return 0; }这里的while (1)是故意写出来的“死循环”配合break在合适时机退出。这是循环设计里的常用思路很多情况下你不知道用户要猜多少次所以让循环一直跑直到满足条件再用break跳出。但需要强调while (1)必须保证循环体内有可靠的退出条件否则程序会彻底卡死。这题如果觉得简单可以再加限制最多只能猜10次超过次数强制结束。这就得引入一个计数器count每次循环内递增到10次直接break。你试着自己改一下就能掌握计数器控制的思路这也是循环里最核心的编程能力。5. 常见错误与排查技巧写C语言头一年90%的时间不是在写代码而是在查代码为什么不对。下面这几个问题是我见过最多的你提前知道这些坑能省下一大把时间。5.1和这是所有C语言初学者都会犯的最经典错误。if (a 1)不会报错它把1赋值给a然后判断“a的值为1”这个条件是否为真——结果是真。这种bug让人百思不得其解明明条件不成立为什么它还是走进去了防止这个错误有一个土办法把常量写在左边if (1 a) { // ... }如果你不小心写成if (1 a)编译器会直接报错因为不能给常量赋值。这个习惯虽然看起来有点别扭但对新手来说它是很实用的“防呆设计”。5.2 悬挂elsedangling else看这段if (a 0) if (a 10) printf(a大于10); else printf(a不大于0);你可能会以为else是和第一个if配对的但实际上C语言规定else总是和最近的未配对if配对所以这个else是和a 10配对的导致代码逻辑反转。避免悬挂else的办法很简单把所有if和else的大括号写完整。一旦用大括号把分支体框起来这个坑就不存在了。5.3 死循环排查程序卡住不退出十有八九是死循环。排查思路是先在循环体里加printf打印当前变量的值看看循环执行到哪一步变量值是否符合预期。如果变量根本没有变化那就是步进没执行或者条件恒真如果变量变化了但条件还是真那可能是条件写错了方向比如小于和大于搞反。对于实在看不出来的可以加一个计数上限比如int guard 0; while (condition guard 10000) { // ... guard; }等代码正常运行了再删掉guard。这是调试中最实用的小技巧比盯着屏幕干看效率高得多。5.4 环境配置VSCode跑C语言很多初学者在安装编译器、配置环境这一步就卡住了。如果你用的是VSCode目前最省心的方式是这样安装MinGW-w64或MSYS2把bin目录加到系统环境变量PATH里。在VSCode里安装C/C扩展。新建一个.vscode/launch.json和.vscode/tasks.json配置好gcc编译命令。用终端手动执行gcc 文件名.c -o 文件名.exe也能跑起来。不少教材一上来就让你用IDE导致你对编译过程完全没有感知。我的建议是前期就算用VSCode也要学会在终端里手动执行gcc命令哪怕只是运行一下“hello.c”也要知道编译和运行是两个步骤。不然以后学了多文件工程你会对“编译链接”完全没概念遇到undefined reference这类报错会慌得不行。6. 练习方向与节奏建议6.1 从“看得懂”到“敲得出来”很多同学学到这里有个误区看例题觉得很简单考试却不会写。原因只有一个——你动手太少。看代码就像看别人骑车轻松自己上车摔跤。我建议你按这个顺序来练习第一轮把书上的例题自己敲一遍跑通就过不要求背。第二轮合上书只看题目要求自己独立写写不出来就看提示看完合上继续写。第三轮把同样的功能用不同语句实现。比如同一个统计正负数个数的程序先只用if写一版再用if else写一版再用switch试试能不能改写。三轮走下来你对分支循环的语法细节才算真正掌握。剩下的就是题量积累。6.2 适合这个阶段的题目类型如果你想找一些练手的题我推荐这几种类型网上搜“C语言基础练习题”能找出一大堆比如“翁恺C语言练习题”就有很多适合这个阶段的题目。优先刷这句话我反复说只要涉及数字处理逻辑回文数、水仙花数、最大公约数、最小公倍数、素数判断都要优先想到循环。这类题专门锻炼你把数学表达式转换为循环逻辑的能力而这正是C语言工程里最常见的场景。还有“必背100代码”这个说法我不建议死记硬背。背代码不是目的理解过程才是。你把100个代码背下来不如亲手把10个经典题的断点、变量变化、输出结果分析清楚。6.3 为后面指针和数组打基础分支循环学扎实了后面接触数组时你会觉得顺理成章数组遍历就是for循环下标数组越界就是循环边界写错了。而指针中的“指向链表的节点遍历”本质也是while循环只不过判断条件和步进方式从整数变成“指针是否为空、指针指向下一个节点”。所以现在花时间把分支循环的基本功练好其实是在给指针和链表打地基。很多学指针学到崩溃的人回头去看翻车原因不是指针太抽象而是循环边界不清楚、分支条件写岔了。7. 下一步循环的进阶玩法由于这一讲是“上”我们只处理了最基础的语法和用法。但实际上循环还有很多更深入的东西等着你。比如for嵌套。for (int i 1; i 9; i) { for (int j 1; j i; j) { printf(%d*%d%d\t, j, i, i * j); } printf(\n); }这是九九乘法表。外循环控制行数内循环控制每行的列数两个变量一旦串到同一个表达式里就是双变量协调非常考验你对“循环变量变化”的理解。还有一个角度是循环和数组的结合你可以把“从1打印到100”升级成“把1到100存进数组再倒序打印”这种练习能让你自然过渡到后面的数组知识点不会感觉突然跳了个台阶。多提一句Fibonacci数列、最大公约数、素数筛选这些题它们不是刷题工具是让你感受“同一个问题用循环的不同写法都能有不同优雅程度”的切入点。比如求最大公约数的辗转相除法用while写只有几行但有人会写成一大坨if嵌套——对比一下你就知道什么是好代码了。分支和循环的上篇就聊到这里。如果你能把这篇文章里的代码全部自己敲一遍再试着改一改条件、换一换循环顺序那你基础这一关已经稳了。下一次我们会聊循环的嵌套、break和continue更复杂的组合以及如何用循环优雅地处理实际问题到时候见。