ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

C语言分支与循环语句详解:if、switch、for、while及避坑指南

C语言分支与循环语句详解:if、switch、for、while及避坑指南 很多学C语言的朋友都有过这样的经历单独看if和for的语法都懂一到自己写判断和循环就乱套要么多分支逻辑调度不清晰要么循环条件里埋了死循环和越界的雷。说穿了分支和循环语句就是C语言的骨架程序里几乎九成的逻辑都落在怎么判断和怎么反复执行这两件事上。这篇东西就把我在一线写C、带新人过程中积累的经验摊开来聊先讲清楚每种语句背后的设计逻辑和适用场景再给出一套可以直接照着写的实操套路最后整理一份踩坑速查表希望帮你把这部分基本功彻底焊死。既然上手写代码绕不开的就是代码风格和工具链。结合这两年大家问得比较多的问题我把 vscode 配置 C语言环境、C-Free 这类开发工具的取舍以及分支循环代码里常见的坑一起讲透。适合正在学C语言的初学者也适合教书带团队的朋友当教学素材参考。1. 先把分支和循环这盘棋想清楚1.1 判断与重复是程序的两条腿任何程序跑起来本质上都在做三件事输入数据、处理数据、输出结果。而处理这一步翻来覆去就是两个动作——根据条件走不同的路以及把同样的操作重复多遍。前者叫分支后者叫循环。你去看任何一段实际项目代码不管它多复杂底层逻辑最终都会被拆成这两种结构的组合。哪怕是用深度循环模型做训练落到代码层面backbone 里的数据处理、梯度累积、epoch 遍历统统是for、while在干活。再比如 Redis 的循环队列、SQL 语句的批量查询、C# 后台开线程做数据采集底层也离不开这套基本功。所以不是我要学分支循环所以看这篇而是我写的任何程序都要靠它撑起来。把这两样东西练成肌肉记忆后面学数组、指针、结构体、文件读写才能把精力放在新概念上而不是每次都被老问题绊倒。1.2 学语句别只看语法要看它解决了什么烦恼很多教材讲if就只丢给你一份语法格式然后让读者自己背。这其实是本末倒置。你在这个阶段真正该建立的意识是C语言为什么给了你这么多种分支工具又给了你这么多种循环工具分支工具给了if、else if、switch因为在不同场景下代码的可读性和执行效率诉求不一样。比如单条件判断用if就够了多条件逐级判断时else if比嵌套if清爽而判断一个整数取值落在哪个区间时switch的匹配逻辑天生更清晰。循环工具给了while、do-while、for同样是为了适配先判断再执行和至少执行一次这两种截然不同的需求以及让循环变量的初始化、条件判断、步进更新能在一行里被看见。带着它解决了什么问题的心态去学你会发现这些东西根本不用硬背用到的时候自然就会选。2. 分支语句全景拆解if 就是程序的红绿灯2.1 if 与 if-else 的基本姿势代码里的分支本质上就是给程序装红绿灯。绿灯放行、红灯停下、黄灯犹豫一下走另一条道每一条分支都必须有一个条件表达式条件成立执行一段不成立就绕过去或执行另一段。实际写的时候我强烈建议哪怕是只有一条执行语句也把大括号写全。你可能会觉得if (a 0) printf(yes);这样更简洁但等你自己回头改代码或者别人帮你 review 的时候没大括号的if就是一颗定时炸弹——后面加一行你想当然认为属于if的代码结果它根本不在分支里面运行结果驴唇不对马嘴。#include stdio.h int main(void) { int score; scanf(%d, score); if (score 90) { printf(优秀\n); } else if (score 75) { printf(良好\n); } else { printf(需要再加油\n); } return 0; }这段代码的逻辑很直白90 分以上是一档75 到 89 是一档剩下的人手动善意安慰一下。因为用了else if条件之间天然互斥程序会从上往下找到第一个满足的条件执行执行完整个分支结构就结束不会出现同时满足两个条件然后执行两遍的尴尬。这里有个很重要的细节判断区间时两个相邻区间的边界值是重叠还是不重叠直接决定代码正确性。上面例子我先判断 90再判断 75那么 90 分本身只会进第一档如果你顺序反过来先 75再 90那 90 分就会先被拦截在良好里这就是典型的边界判断次序错误。写边界条件时最好拿典型值、边界值、异常值三种数据各测一遍。2.2 悬空 else 是怎么发生的悬空 else 属于C语言里出了名的坑而且新手几乎百分百会踩一次。规则只有一句话else总是与离它最近的、尚未匹配的if配对。但这句话的真实后果很多人要踩过才知道。看下面这段代码int a 1, b -1; if (a 0) if (b 0) printf(positive\n); else printf(negative\n);光看缩进你肯定以为else配的是外层的if (a 0)觉得逻辑是a 大于 0 且 b 不大于 0 时打印 negative。但实际编译器会把它配对成a 大于 0 时进入内层判断如果 b不大于 0 则打印 negative。换句话说else陪的是内层那个if (b 0)跟缩进完全没有关系。这段程序跑起来因为 a 0 成立进入内层b 不大于 0于是输出 negative。看起来好像歪打正着可一旦 a 改成负数输出就变成什么都不打印彻底暴露配对错误。解决办法就是前面说的大括号每层if都显式加括号。别跟编译器玩猜谜游戏写清楚最省心。2.3 switch-case 是整型分支的利器当分支的判断条件是一个变量和多个离散常量比较时switch往往比一连串else if更合适。它天生就是把一个整数拿到多个 case 里去撞撞上谁执行谁一个都撞不上就执行default。这个语义非常适合做菜单选择、命令解析、状态机这类场景。#include stdio.h int main(void) { int cmd; printf(请选择操作1-查询 2-新增 3-删除 0-退出\n); scanf(%d, cmd); switch (cmd) { case 0: printf(退出系统\n); break; case 1: printf(执行查询\n); break; case 2: printf(执行新增\n); break; case 3: printf(执行删除\n); break; default: printf(无效指令请重新输入\n); break; } return 0; }这种写法比起写三个else if结构的意图一目了然cmd 这个变量每一种取值对应一条路。而且switch在编译器层面通常会被优化成跳转表多个分支时性能也比逐个比较的else if更好有兴趣的话可以了解一下 FPGA 综合工具对多分支 case 语句的处理道理有相通之处。switch有几个强制规矩记不住就会出事case 后面必须是整型常量表达式不能用变量也不能用浮点数。每一个非空 case 块最后不要忘了break否则会执行穿透也就是 fall-through把下面所有 case 的代码顺序执行一遍直到遇到 break 或 switch 结束。同一个 switch 里case 值不能重复。有时候穿透也被当作技巧故意使用比如多个 case 共享同一段处理逻辑switch (grade) { case A: case B: printf(通过\n); break; case C: printf(刚好及格\n); break; default: printf(未通过\n); break; }这种写法没问题但故意穿透时必须写清楚注释否则很容易吓到后来维护代码的人。我见过不止一次代码里忘写break导致连续执行了好几个功能查了半天才发现是穿透问题教训非常深刻。2.4 if-else 链和 switch 怎么挑简单粗暴的规则是条件在比较区间、大小、范围或者需要做复杂逻辑运算时用if-else条件在拿一个整型变量和几个固定离散值撞时用switch。比如判断成绩及格不及格用if解析用户输入的数字菜单用switch。还有一种情况当你同时需要判断多个彼此独立的条件而不是互斥条件时就不要把它们写成层层嵌套的else if那样会把你自己的逻辑都绕晕。把条件拆成独立的if或者用逻辑运算符组合更清爽也更容易测试。3. 循环语句逐一说透for、while、do-while3.1 为什么需要三种循环循环的本质是重复执行一段代码但重复这件事也有不同姿势有的要先判断再执行有的要先做一次再说有的需要清晰地管理计数器。C语言干脆给你三样工具各管一摊。学习的时候我最反感的一种教法是让新手把三种循环都背一遍然后考试时搞语法默写。有用吗暂时有转头就忘。真正管用的办法是理解每种循环解决的真实场景再去写代码验证。3.2 while先看门再进门while (条件) { 循环体 }的执行顺序是先判断条件成立不成立成立才执行一遍循环体然后回来再判断。如果条件一开始就不成立循环体一次都不执行。典型的应用场景是你不知道到底要循环多少次只知道在什么条件下继续、在什么条件下停。比如从标准输入不断读取数据遇到 0 就结束或者做数值迭代直到误差小于某个阈值才停止。#include stdio.h int main(void) { int sum 0; int n; printf(请输入多个正整数以0结束\n); scanf(%d, n); while (n ! 0) { sum n; scanf(%d, n); } printf(总和是 %d\n, sum); return 0; }这段代码每读一个数、加一次再读下一个数读到 0 就说明输完收工。条件n ! 0就是继续循环的门票。这个模式在真实项目里非常常见C语言文件读写、C# 循环数据采集、SQL 结果集遍历思路全是一模一样。写while最尴尬的事情是忘了更新循环变量。比如上面如果漏了scanf(%d, n);n 永远不等于 0循环就成永动机了。所以写完循环体先检查一遍条件里用到的变量在循环体里有没有一条路能让它朝退出的方向变化。3.3 do-while先进门再查票do { 循环体 } while (条件);的独特之处在于它先执行一次循环体再判断条件。这保证了循环体至少运行一次。这在某些场景下是刚需。最典型的例子就是菜单程序。你希望用户至少看一眼菜单、至少能操作一次然后循环询问他是否要继续还有一种输入校验场景先让用户输入一次如果输入不合法就让他重新输这也天然适合 do-while因为总得先输入才能判断合不合法。#include stdio.h int main(void) { int choice; do { printf(菜单1-开始 2-设置 0-退出\n); printf(请输入); scanf(%d, choice); switch (choice) { case 1: printf(开始运行\n); break; case 2: printf(进入设置\n); break; case 0: printf(程序退出\n); break; default: printf(输入无效\n); break; } } while (choice ! 0); return 0; }注意 do-while 的结尾分号千万别丢。这是语法里唯一一个循环/分支语句后面还要加分号的地方。很多人第一次写都是死在这。3.4 for把计数器管得明明白白for循环把三件事压缩到一行初始化循环变量、判断循环条件、更新循环变量。它的最大优势是循环的三要素集中在一起别人读代码的时候一眼就能看到循环怎么开始、怎么结束、每次怎么步进不容易出现 while 里常见的忘更新变量问题。最常见的是固定次数的遍历比如数组、字符串、打印九九乘法表、做累加#include stdio.h int main(void) { int sum 0; for (int i 1; i 100; i) { sum i; } printf(1 到 100 的总和是 %d\n, sum); for (int i 0; i 5; i) { for (int j 0; j i; j) { printf(*); } printf(\n); } return 0; }这里有两个细节很容易被忽略第一个for (int i 1; i 100; i)用的是循环体执行 100 次。如果你写成i 100那就变成 99 次。很多字符串处理的循环正确边界是 strlen(s)多一个等号就可能把结尾的\0也遍历进去甚至越界访问。所以 for 循环的边界条件务必拿实际例子心算一遍。第二个for 的三个表达式理论上都可以省略但省略时必须保证循环逻辑能正确结束。比如for (;;) { // 相当于 while(1)死循环通常配合 break 使用 }死循环本身不是罪过很多服务器主循环、驱动事件循环就是永不停歇的。但死循环里头必须要有break或者可以退出程序的条件否则你的程序就跑成赖着不走了。3.5 三种循环怎么选一张对照表循环类型执行顺序最少执行次数典型场景while先判断条件再执行循环体0 次不知道循环次数只知道继续/停止条件do-while先执行循环体再判断条件1 次至少执行一次例如菜单、输入校验for初始化、判断、步进集中在头部0 次已知循环次数遍历数组/字符串/计数器没有绝对的好坏按场景选。你如果觉得我要写一个循环 N 次优先 for如果是持续读数据直到某个条件优先 while如果是先给用户看一次菜单再决定是否继续优先 do-while。4. break 和 continue循环的刹车与免跑单4.1 break 跳出整个循环break用在循环里作用是立刻跳出当前这一层循环后面所有未执行的迭代都取消。它和switch里的break都是退出当前结构但要注意作用范围它只退出最近的那一层不能跨层。经典场景是在循环里找到了目标就不需要再傻乎乎找下去了#include stdio.h int main(void) { int numbers[] {3, 7, 12, 5, 8}; int target 12; int found 0; for (int i 0; i 5; i) { if (numbers[i] target) { found 1; break; } } if (found) { printf(找到了目标值下标是 %d\n, find_index); } else { printf(没有找到\n); } return 0; }这段代码里break一旦命中就立刻跳出 for后面数组元素不再比对。这就是提前结束循环的典型用法比遍历完整个数组再判断节省时间逻辑也更清晰。但注意break只跳一层。双层循环里内层 break 后外层还在继续跑。如果你是想同时退出两层通常不能依靠 break 的默认行为去赌而是设一个标志变量外层每轮先看标志再决定是否继续。4.2 continue 跳过本次迭代continue的作用是结束当前这一次迭代直接跳到下一次循环判断。它不退出循环只是这一轮先不干了。在循环体里continue 之后的所有代码都被跳过直接回到循环头部。对于 for 循环会先执行步进表达式再判断条件对于 while会直接进入下一次条件判断。这个差异很重要因为 continue 配合 while 容易顺手把循环变量更新写漏了。int n 0; while (n 10) { n; // 确保更新在 continue 之前 if (n % 2 0) { continue; // 跳过偶数打印 } printf(%d , n); }输出结果是 1 3 5 7 9。如果你把n放在continue之后一遇到偶数就 continue循环变量永远不增加直接死循环。这不是 continue 的错是循环更新位置选错了。4.3 break 和 continue 别乱嵌套很多新手写多重循环时脑子一发热在内层循环里写了一堆 break 和 continue最后自己都说不清程序会走到哪。这里我建议一条纪律如果循环超过两层优先考虑把内层逻辑抽成函数或者用标志变量控制退出而不是无限堆 break。举个例子要在一个二维数组里找到第一个值为 -1 的位置双层循环需要两层退出逻辑int matrix[3][3] { {1, 2, 3}, {4, -1, 6}, {7, 8, 9} }; int target_row -1, target_col -1; int found 0; for (int i 0; i 3 !found; i) { for (int j 0; j 3; j) { if (matrix[i][j] -1) { target_row i; target_col j; found 1; break; } } }这里外层循环条件里加了!found内层 break 之后外层判断 found 为真也会结束。这种写法比单独靠 break 更容易读也更好排查问题。5. 实操把分支循环组合成能跑的程序5.1 综合案例写一个数字猜谜小游戏把分支和循环放在一起练手最合适的入门项目就是猜数字。程序随机生成一个 1 到 100 的整数然后循环提示用户输入判断输入值和目标值的大小关系直到猜对或者次数用尽。这个程序用到了 for/while 循环、if 判断、break 跳转一石三鸟。#include stdio.h #include stdlib.h #include time.h int main(void) { int secret, guess; int max_attempts 7; srand((unsigned)time(NULL)); secret rand() % 100 1; printf(我已经想好了一个 1 到 100 之间的整数你有 %d 次机会。\n, max_attempts); for (int i 1; i max_attempts; i) { printf(第 %d 次猜测, i); scanf(%d, guess); if (guess secret) { printf(恭喜你猜对了\n); return 0; } else if (guess secret) { printf(太大了\n); } else { printf(太小了\n); } } printf(机会用完了正确答案是 %d。欢迎再玩一次\n, secret); return 0; }这个程序把for控制次数、if-else判断大小、return 0提前结束、边界值rand() % 100 1全部串在了一起。写完跑一遍你会发现代码的每一处基本都离不开分支和循环的语言要素。5.2 开发工具与配置VSCode 和 C-Free 的取舍写 C 代码之前顺手把环境搞定。我见过太多人把时间浪费在配置工具链上绕了一圈又想放弃。如果你用的 Windows 系统最省事的方案是安装 C-Free 5.0它自带编译器C-Free 5 内置了 MinGW 的一个版本新建文件就能写、能编译、能运行面向刚学 C 的同学非常友好。缺点是它已经年代久远界面老旧但用来跑通语法和简单项目完全够用。如果你想用 VSCode 写 C那么配置 C 语言环境的核心步骤是先安装编译器MinGW-w64 或者 MSYS2再把编译器的 bin 目录加到系统 PATH 环境变量最后在 VSCode 里安装 C/C 扩展用 tasks.json 配置编译命令。网上有不少图文教程照着做就能跑通。配置完记得在终端执行gcc --version验证一下看到版本号输出就说明编译器装好了。这一步如果卡住先检查 PATH 里有没有刚才加的路径几乎九成的报错都是环境变量没生效。C-Free 和 VSCode 怎么选我的建议是纯新手阶段用 C-Free 把精力集中在语言本身等你开始写那种几十个文件的项目、需要依赖分析和 Git 版本管理时再切换到 VSCode 加插件的工作流。现在网上也常有人问VSCode 配置 C语言环境遇到各种报错我建议你先用 C-Free 跑通第一个程序对 C 的编译过程有感觉后再折腾 VSCode 也不迟。工具是拿来用的不是拿来磨的。5.3 分支循环场景的通用代码模板很多时候你需要的不是一整个项目而是一个能直接套用的片段。我把日常开发中最常用的模板整理一下输入校验循环读入一个值不合法就重新读用 do-while 最自然。菜单循环do-while 包住 switch用 while 条件判断是否退出。固定次数累加for 循环加累加器。找目标值提前退出for 循环加 break 加标志变量。嵌套打印图形/表格外层控制行内层控制列例如九九乘法表、直角三角形、菱形。这些模板背下来不值得动手各写一遍肌肉记忆自然就有了。写的时候给每个循环起一个有意义的名字比如for (int col 0; col cols; col)而不是for (int i0; i5; i)。这个习惯越早建立越好等代码一行变十行、十行变百行你就知道变量名和意图清晰有多救命。5.4 编程风格大括号、缩进和注释最后再讲一个容易被忽略但很重要的点分支循环代码的可读性。同样一段逻辑有人写得一目了然有人写得像天书。核心区别在于大括号统一要么 KR 风格大括号跟在语句尾部要么 Allman 风格大括号独占一行。别混着用。我推荐统一 KR占的行数少看代码更紧凑。缩进统一用 4 个空格或一个 Tab别一会儿空格一会儿 Tab。很多编辑器交换过 Tab 和空格后代码乱掉就是混用造成的。注释说意图不说废话// 如果总分大于90就打印优秀这种注释没有价值应该写的是// 成绩等级按总分划分边界条件90分属于优秀。搞项目不是写给自己一个人看的。C语言遍布底层芯片、操作系统、嵌入式、数据库内核团队协作是常态代码风格差异如果太大合并分支时 Git 冲突能把你烦死。这也是为什么很多团队从一开始就对代码格式有严格规范。我的经验是新手阶段就养成一致的格式化动作任何环境里写完代码随手格式化这个好习惯能省掉后续无数 review 争吵。6. 常见问题与排查技巧实录6.1 快捷键映射、条件编译等周边概念有位读者之前问过我说自己在 VSCode 里写完代码编译报错一堆问是不是编译器坏了。我让他把报错贴出来发现全是未定义符号 main他其实编译的是一个只有函数没有 main 的源文件。这里顺带说三点C 程序的入口必须是main。编译可以生成.o目标文件但可执行程序的链接阶段必须找到main否则链接失败。想在多个平台写不同分支逻辑可以用#ifdef条件编译。这不是运行时分支是预处理阶段的分支用来处理不同操作系统的差异代码。比如 Windows 和 Linux 下某些系统 API 不一样就可以用#ifdef _WIN32包两套实现。C 语言来源年份虽然久远但至今仍是嵌入式、操作系统、数据库实现的主流语言。学分支循环时顺便了解一点编译原理预编译、编译、汇编、链接会对理解条件编译为什么在编译期就决定了很有帮助。6.2 死循环怎么快速定位死循环是分支循环阶段最让人头疼的问题。程序跑起来不结束按 CtrlC 也未必能停只能崩溃掉。我的排查顺序是先看循环条件里涉及的变量有没有被正确更新。最典型的错误是 while 写成了while (i 10)循环体里却忘了i或者i被 continue 跳过了。再看循环边界有没有可能永远不满足。比如for (i 0; i n; i--)方向写反了i 永远小于 n直接就死循环。最后看是不是循环体里逻辑上有多个条件互相制约导致某条路径下条件永远不改变。如果实在一眼看不出来可以临时在循环体里加一个打印语句输出当前变量值。比如while (n 0) { printf(n %d\n, n); // 其他逻辑 }看到输出卡在哪个值附近基本就定位了。这类问题十有八九是边界和更新的问题跟语言本身没关系排查时必须冷静。6.3 悬空 else、运算优先级与常见编译报错悬空 else 我们已经提过。除了它另外一个常搞混的是运算优先级。if (a 1 0)这种写法里优先级比高所以实际是a (1 0)跟你想的完全不一样。遇到复合表达式时不要装酷直接用括号把意图写清楚。编译器不会嫌括号多但会默默按错误的优先级执行你写错的代码。常见编译报错基本就那几类括号不匹配、分号漏在奇怪的地方、case 标签后面跟了变量、字符常量用了双引号、比较浮点数时用了精确等于。这些报错信息你在 C-Free 或 VSCode 里看多了基本一眼就能识别。我的建议是把编译器报错当成学习资料先读第一行找到文件和行号再回头核对代码不要看到一片红就先慌了。6.4 常见问题速查表问题可能原因解决方法程序不进入分支直接跳过条件表达式写反比如if (a 0)用了赋值而不是等于检查是否写成避免在条件里做赋值满足了条件却不走分支变量类型不匹配浮点与整数比较有精度问题转成同一类型再比较区间判断用范围比较switch 执行了多个 case忘写 break发生穿透每个非空 case 检查是否有 break循环次数多了 1 次或少了 1 次边界写错比如i n与i n弄混用具体数值代入推算执行次数死循环循环变量未更新或 continue 跳过更新语句检查循环变量是否在每次迭代都朝退出方向变化编译报错expected ;分号漏写尤其 do-while 结尾检查 do-while 尾部是否有分号VSCode 编译报gcc 不是内部或外部命令编译器未安装或 PATH 未配置安装 MinGW-w64并把 bin 目录加到 PATH浮点比较总是出问题浮点数在二进制里不能精确表示用差值绝对值小于阈值的方式比较这张表你可以留在手边后面自己调试程序时对着排查比到处翻资料高效得多。我个人带新人这两年最大的体会是分支和循环能不能写好不取决于背了多少语法而是取决于有没有在真实 Debug 里摔过跤。悬空 else、死循环、break 穿透、变量更新位置弄错这些坑踩一次比看十遍教材都管用。建议你把这篇文章看完后把文中的代码手敲一遍再自己改几个版本比如把猜数字游戏的次数改成无限次、把打印三角改成打印菱形、把成绩分级改成 switch 版本。改着改着你会发现分支循环真正长在你脑子里了。祝写码顺利。
返回列表