多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

编程核心:深入理解分支结构与循环结构的实战指南

编程核心:深入理解分支结构与循环结构的实战指南 认认真真敲了这么多年代码我越来越觉得一件事所谓编程能力说到底就是你有多会用if和for。你可能不信但可以翻一翻手头任何一个项目的核心逻辑剥掉那些花哨的框架和工具最后剩下的骨架八成都是判断和循环两种结构的排列组合。分支结构和循环结构是所有编程语言的灵魂双生子是程序从从上往下念一遍升级为能对付千变万化的数据的起点。这正是我特别想聊它们的原因。不管是刚开始学 C、Python 还是 Java 的初学者还是已经工作几年但总觉得自己逻辑不够硬的老兵把这两个结构吃透都能让代码水平上一个明显的台阶。网上讲 if 和 for 的教程多如牛毛但大多只讲语法不讲为什么这么设计更不讲实战里最容易在哪儿翻车。这篇文章我就从底层原理、三种循环的脾气差异、以及一组贴近真实场景的练习把分支和循环掰开揉碎说一遍。你不用是天才照着文章里的思路走一遍再回过去看那些不照着书上抄就写不出来的题会突然顺眼很多。1. 为什么程序需要分岔路口从念菜谱到做决策先打个比方。一个完全没有分支和循环的程序就像一份只能从头念到尾的菜谱第一步烧水第二步放面条第三步关火念完就完了。现实显然没这么简单——你总得先看看水开了没有开了才放面条没开就再等等煮的过程中还要反复判断面条软硬程度软了就关火不软就继续煮。菜谱一旦加上了根据当前状态决定下一步做什么就不再是一份死板的清单而是一个能应变的操作指南。程序本质上也是这么回事。计算机老老实实默认情况下就是从上到下逐条执行指令这叫顺序结构也是程序的默认流水线。但实际需求里几乎没有哪段逻辑是真正一路无脑执行到底的。用户输入的年龄是 18 还是 70库存数量是 0 还是 120网络请求是成功还是超时——数据稍微变一下程序要做的处理就完全不同。这时候程序必须在某个位置问自己一个问题根据答案选择不同的路继续走。这个问问题、选分支的机制就是分支结构。我在带新人或者给在校生辅导时经常遇到一种情况很多人能把语法背得滚瓜烂熟可一旦拿到一道键盘输入三个数按大小排序输出的题就卡住怎么想都绕不明白。后来我发现卡住的根本原因不是不懂 if而是脑子里没有一个程序执行轨迹的图景。你跟着自己写的代码一行一行走一遍把不同情况下的走向画出来立刻就能看清原来程序走到某个 if 时数据是从左边还是右边走的我是不是漏了一个情况。说个和学习直接相关的很多人会在头歌实验四 分支结构这类平台实验上花大量时间。其实实验本身并不难难的是平台给的是零散的填空和纠错题没有帮你建立整体框架。你在那儿一道题一道题地试错效率极低。正确姿势是先花十分钟在纸上画出每个分支的走向再动手补代码。后面我会用一组练习给你演示什么叫先画路径再下笔。2. 分支结构的三种写法与它们的脾气分支结构在绝大多数主流语言里都有两套表达方式一套是if家族一套是switch家族。风格完全不同适用场景也不同。2.1 if 单分支与 if-else 双分支最朴素的非此即彼单分支的形态最简单条件成立执行一段代码不成立什么都不做。很多语言里它长这样if (score 60) { printf(恭喜及格了\n); }用 Python 写也类似if score 60: print(恭喜及格了)双分支就是加一个else意思是如果条件不成立走另一条路if score 60: print(及格了) else: print(不及格好好反思一下)这里有一个非常关键、但新手几乎人人踩过的坑if的成立与不成立必须是互斥且穷尽的。也就是说任意一份数据进来它总能且只能落进一个分支。比如上面这个例子大于等于 60 和小于 60把所有分数情况完全划成了两半不可能出现既不是及格又不是不及格的分数。可一旦你自己把条件写得模棱两可就会出现某些数据不知道该走哪或者走了不该走的路的诡异场景。举一个我见过的经典错误——判断成绩等级时写成if score 60: print(及格) if score 85: print(优秀)如果输入 90 分会同时打印及格和优秀两个结果。看起来没问题问题大了。你的本意是分档但这两个 if 是独立的90 分同时满足两个条件两条路都走了。这就是没有互斥造成的结果。换个写法if score 85: print(优秀) elif score 60: print(及格) else: print(不及格)这种情况下85 分以上的走第一分支60到84分走第二分支其他走第三分支三条路完全分开任何分数都只会命中一条。多条件判断的第一原则就是让条件一个有层次地收缩而不是一堆平级 if 乱飞。2.2 else if 与逻辑运算符把复杂决策拆成清晰链条当决策分支超过两条比如把成绩分成优秀、良好、及格、不及格四档就不能只靠一个 if-else 了。几乎所有语言里都会用else if或者elif把条件串起来if (score 90) { printf(优秀\n); } else if (score 75) { printf(良好\n); } else if (score 60) { printf(及格\n); } else { printf(不及格\n); }这串条件用的是同一个套路从上到下逐个判断命中谁就执行谁执行完整个 if-else 链立即结束后面的条件不再挨个检查。所以条件的顺序本身就是逻辑的一部分写反了就会闹出90 分被判为良好的笑话。还有一个容易被忽略的地方多条件综合判断时要用好逻辑运算符与、||或、!非。比如判断一个年份是不是闰年标准是能被4整除但不能被100整除或者能被400整除。翻译成条件就是if (year % 4 0 and year % 100 ! 0) or (year % 400 0): print(闰年)这个例子是教科书级的因为它逼你同时处理数学运算、比较运算、逻辑运算的优先级。实际写代码时我的建议是别依赖优先级记忆直接上括号。括号不但让读代码的人一目了然还帮你减少我以为优先级是这样的脑补错误。你永远要假设第二天来看代码的同事或者两周后的自己是个暴躁且没耐心的人他们不该为你的优先级炫技买单。2.3 switch 的匹配思维什么时候它比 if 更好使if处理的是判大小、判范围、判复杂组合那如果场景变成根据一个变量的具体值决定做不同的事呢比如输入 1 执行加法输入 2 执行减法输入 3 执行乘法——这是一组离散的等值判断。用 if 当然能写但一串if (op 1) ... else if (op 2) ... else if (op 3)读起来非常冗长。这种场景就该switch上场。C 语言和 Java 的 switch 逻辑类似switch (op) { case 1: result a b; break; case 2: result a - b; break; case 3: result a * b; break; default: printf(无效操作\n); break; }新手第一次接触 switch 几乎都会折在break上。这是 switch 的一个祖宗级设计每个 case 只是入口标记执行完当前 case 不会自动退出整个 switch而是会继续往下穿透到下个 case除非遇到 break。所以忘写 break 的后果不是编译报错而是运行结果莫名其妙——比如选 1 却连减法也一起执行了。用一句话记住switch 匹配到入口后就顺势滑落break 是唯一的刹车。另外注意很多现代语言比如 Python 在很长一段时间里压根没有 switch后来 3.10 推出了match-case。如果你写 Python没必要非要模仿 C 去拼一个 switch 出来老老实实 if-elif-else 就完全够用。选 switch 还是 if标准只有一个是不是简单的等值离散判断以及判完是否各走各路互不干扰。是用 switch不是用 if。3. 循环的三件套for、while、do-while 到底该怎么选如果说分支结构是程序的岔路口那循环结构就是程序的绕圈圈能力。没有循环你想让程序打印 1 到 100就得写一百行输出语句荒唐且不现实。循环就是告诉你重复执行同一段代码直到某个条件不满足为止。所有循环本质上就两件事一段要重复的代码和一个决定什么时候停下来的条件。围绕着这两个核心出现了三种主流写法脾气完全不同。3.1 while先问后做可能一次都不做while的逻辑最直白每次执行循环体之前先判断条件成立才继续不成立就整段跳过。它的特点是先问再走。int i 1; while (i 5) { printf(%d\n, i); i i 1; }这段代码打印 1 到 5。如果你把i初始值改成 6那么第一次判断6 5就是假的循环体一次都不会执行。这个可能一次都不执行的特性是用 while 时的一个重要心理预期循环不保证你一定会进去。while 最适合的场景是我不确定要循环多少次只知道一个继续进行的条件。典型例子是处理用户输入比如不断读取整数直到用户输入 0 才结束。这种写法你没法事先算好循环次数只能边跑边问。3.2 do-while先做再问至少执行一次do-while是 while 的镜像先把循环体执行一遍再判断条件。它保证循环体至少执行一次。int num; do { printf(请输入一个正数); scanf(%d, num); } while (num 0);这是 do-while 最经典的用武之地你要先让用户输入再根据输入决定要不要重新来一遍。如果用 while 写你得在进入循环前先病态地初始化一个肯定符合条件的值或者把输入语句在循环外和循环内各写一遍非常别扭。do-while 就是为这种至少跟用户要一次输入的场景量身定做的。很多初学者不理解为什么 C 和 Java 会有三种循环觉得 for 够用了。其实这三种写法不是彼此替代关系而是没得选语法恰好能最准确地表达意图。如果你想表达先执行后判断的必然性用 while 就得加额外标记变量既不自然还容易出错不如直接 do-while。3.3 for计数循环的标准答案for是我日常用最多的循环因为绝大多数重复任务都可以归纳成从某个初始值开始按固定步长走到终点。C 系的 for 把三要素——初始化、条件判断、步进更新——全部压缩进一行for (int i 1; i 5; i) { printf(%d\n, i); }对应 Python 的写法是for i in range(1, 6)逻辑类似。for 最大的好处是把怎么循环集中管理你一眼就能看到起点、终点和步长不容易漏掉循环变量的更新。但这里有个超级常见的坑我见过太多人边写for (int i 0; i n; i)边在循环体内部偷偷改i的值比如i i 2。不是说绝对不能但这种一个循环两套节奏的写法极易导致跳步、死循环或者莫名其妙的越界。for 的循环变量更新是官方指定的如果要额外跳过某些迭代正确姿势是用continue或干脆让步进表达式变得更复杂而不是在循环体里又偷偷动刀。三种循环的取舍用表格总结会更直观循环类型判断时机最少执行次数典型场景while先判断后执行0 次不知道循环次数只知继续条件do-while先执行后判断1 次必须至少执行一次的任务for先判断后执行0 次明确知道循环范围和步进规则再补充一个 C 系语言里常见的尸体级错误死循环。死循环的常见成因有三种忘了更新循环变量、更新了但条件永远成立、或者在 while 的条件里下意识写了赋值而不是比较。我调试过最诡异的一个死循环就是同事把while (status 0)写成了while (status 0)结果循环每跑一次都把变量置 0条件永远成立程序像吃了炫迈一样根本停不下来。这种错误编译期几乎发现不了只能靠调试揪出来——所以还是那句话能用的地方别懒惰。4. break、continue 与嵌套循环突破单层思维很多人在掌握单层循环后发现还是玩不转因为现实问题里循环很少是孤独一根而是嵌套的外层跑一圈内层跑所有圈。处理嵌套循环的关键是彻底理解break和continue到底作用于哪一层。4.1 两个控制词的精确打击范围break的作用是立刻终止当前这层循环然后跳到这个循环后面的代码。注意关键词是当前这一层。如果你在一个三层嵌套循环的最内层写 break它只会跳出最内层外层该跑还是继续跑。想一次跳出多层不好意思没有这么智能的 break至少 C 和 Java 的标准版没有。实际工程里如果真的要跳出多层循环通常用两种办法一是用 flag 标记内层置标记外层每圈检查标记再 break二是把多层循环封装成一个函数用 return 直接返回。continue的作用是跳过本次迭代的剩余代码直接进入下一次循环。它跟 break 的区别可以这样记break 是这层循环我不玩了直接结束continue 是这次循环我不玩了跳到下次。比如打印 1 到 10 之间除 5 以外的数字for (int i 1; i 10; i) { if (i 5) { continue; } printf(%d\n, i); }5 被跳过其他全部正常打印。很少有人会提醒你的是continue 在 while 里要比在 for 里更小心。因为 for 的步进更新写在循环头部continue 跳回时自动执行i但 while 的步进更新如果写在循环体末尾遇到 continue 会直接跳过头尾那段更新代码然后条件判断时用的还是旧值轻则死循环重则逻辑错乱。这个细节没踩过坑的人绝对想不到。4.2 双重循环的执行节奏内快外慢嵌套循环的理解难点是如何在脑子里并行地跟踪两个变量的变化。很多人一遇到双重循环就犯晕其实口诀很简单外层循环跑一次内层循环从头到尾跑完一整遍。举一个最经典的例子——输入 3 行每行 5 颗星for (int i 1; i 3; i) { for (int j 1; j 5; j) { printf(*); } printf(\n); }执行轨迹是i1 时内层 j 从 1 跑到 5打印 5 颗星然后打印换行i2 时内层 j 再次从 1 跑到 5再换行i3 时同样再来一遍。整体节奏是外层慢,内层快。做这类题的通用套路是先用小数字手工模拟三轮比如把 3 和 5 改成 2 和 2在纸上把 i、j、输出的对应关系写成一张表看清规律后再推广到原参数。这套方法我一直推荐给所有人比任何讲解都好使。很多作业里会出现打印倒三角打印菱形这类题看着炫酷本质还是双重循环外加分支判断。核心问题是问自己这一行的星星数量跟外层变量 i 是什么关系把这种数量关系用数学式表达出来循环体里再配合 if 控制空格和星号基本就通了。5. 分支和循环的组合拳真实练习与错误排查路线理论讲得再多不动手都是空中楼阁。热搜词里那个分支结构练习勾起了我不少回忆当年我带教学弟学妹时用的就是下面这套从易到难的组合。你按顺序做完对分支和循环的理解绝对会上一个台阶。5.1 练习一判断闰年——培养条件拆分意识题目输入年份输出是否为闰年。闰年的规则我已经在 2.2 里写过但更值得学习的是把中文规则翻译成代码时的自查流程。拿到题先别急着敲在草稿纸上回答这三个问题闰年有哪几种情况这几种情况之间是或还是与边界值应该用哪个年份去验我用这个方法教学效果立竿见影。大家最容易犯的错误是只写了year % 4 0漏掉整百年必须被 400 整除的规则导致 1900 年被误判为闰年。1900 年确实能被 4 整除但它是整百年必须看能否被 400 整除——所以它不是闰年。这个测试用例是所有闰年程序公认的试金石。5.2 练习二求 1 到 n 的累加和——循环基本功题目输入 n计算 1 2 ... n 的和。这题没有任何分支但能让你彻底搞清楚循环变量的三种角色计数器、索引器、累加器。i是计数器从 1 到 nsum是累加器每轮加上当前的 i。循环这么写int sum 0; for (int i 1; i n; i) { sum i; } printf(%d\n, sum);我特别强调过sum必须初始化为 0。别笑真的有大二学生在这上面翻车——初始化成 1 或者忘了初始化结果得出一个所有数都加偏的答案。累加器的初始化值是循环所有变量里最容易被忽略、又最致命的一环。5.3 练习三成绩等级分档——多分支与条件顺序的完美案例题目按分数输出优秀/良好/及格/不及格。这题我在 2.2 给过一版了现在把它做成练习目的是让你明白条件的顺序有多重要。你可以亲手做一次小实验先把条件从低分数开始写比如先写if (score 60)再写elif (score 75)然后输入 90 分看结果是不是变成了及格。因为 90 分最先满足的是大于等于 60后面的分支根本没机会执行。这个实验做完你就永远记住了多条件组合必须从最严格的条件开始往最宽松的方向排列。5.4 练习四数字反转——分支和循环的第一次真正合体题目输入一个整数输出它的逆序数。比如输入 12345输出 54321。注意负数要保留负号个位是 0 的不用管前导零。这题是全面提升题因为它同时考验循环取位、分支处理符号。核心思路不断取原数除以 10 的余数把它往结果的末尾追加再把原数除以 10 去掉最后一位重复直到原数变成 0。代码骨架如下int n, reversed 0; printf(请输入一个整数); scanf(%d, n); int sign 1; if (n 0) { sign -1; n -n; } while (n 0) { reversed reversed * 10 n % 10; n n / 10; } reversed reversed * sign; printf(逆序数是%d\n, reversed);不到 15 行代码却把分支处理负数、循环反复取余取整、累加式构造新数反复乘 10 加余数全串在了一起。你能独立把这个思路写出来而不是对着参考答案一行行吞就说明分支和循环的基本功已经过关了。5.5 排查路线做题卡壳时的标准检查顺序在我的经验里初学者跑通这些练习时遇到的报错和结果错误绝大多数能归纳为下面这几类。按照这个顺序排查效率最高语法层有没有缺分号、少括号、if 和 for 后的条件是不是用对了括号包裹这类错误编译器会直接报错相对好解决。变量层累加器初始化了吗循环变量在循环体里有没有被意外改动比较运算里有没有把写成逻辑层分支条件之间是否互斥且穷尽条件的判断顺序是不是从最严格到最宽松循环边界是i n还是i n多一位少一位就是天壤之别。边界层用最小输入、最大输入、特殊输入0、负数、个位为0的数各测一遍看程序在边界处是否崩溃或给出反直觉的结果。这个检查顺序我私底下叫四层排查法从编译到运行逐层下沉。很多人一遇到 bug 就立刻怀疑自己逻辑不行其实大部分只是变量初始化或者边界问题按这四层走一遍自己能解决八成问题。写在最后的一点实际感受带过的学生多了我发现真正把分支和循环练透的人和只会套语法的人写出来的代码一眼就能分辨。前者拿到题目脑子里先有执行路径图再落笔成码后者上来就敲敲到哪儿算哪儿然后被各种边界情况打得措手不及。我个人的经验是每次动手写代码之前花两分钟在草稿纸上画一画数据从入口进来之后会碰到哪些判断、可能走哪几条路、每条路最终落在哪儿看起来多花了点时间实际上省掉了数倍的调试时间。最后再分享一个小习惯每次学完一个分支或循环的新写法别只做平台上的填空题自己找一道生活中的小需求比如购物满 300 减 50会员再打九折算最后应付金额逆序输出一句英文的单词之类从零到一写一个完整的程序出来。你会发现平台填空和从一张白纸开始写之间横着一条不小的沟而跨过那条沟的唯一方式就是像上面这些练习一样亲手画路径、亲手验边界、亲手改 bug。把这两个结构练到肌肉记忆的水平后面学函数、学数组、学算法全都是在给这个地基添砖加瓦。
返回列表