1. 三种循环画在同一张纸上,差异一眼就看出来了
刚开始学编程那会儿,我最烦的就是画流程图。代码写三行跑通了,老师非要我画一张图,说是“把逻辑理顺”。直到后来帮别人排查一个死循环问题——代码看半天没毛病,把图一画出来,发现判断框的回流线指错了地方,指到了初始化框上面,那个变量永远被重置,循环永远出不来——我才明白流程图这东西的价值不在于交作业,而在于它能把你脑子里的模糊逻辑逼成一个看得见的形状。
for、while、do while 这三种循环,是 C 语言、Java、Python、JavaScript 乃至 shell 脚本里最基础的三种循环结构,也是各类考试、答辩、软著文档、毕业设计里出现频率最高的流程图元素。标题里说的“流程图画法”,本质上不是美术问题,而是语义问题:你得先搞清楚这三种循环各自在什么时刻做判断、什么时候执行循环体、变量在哪里被更新,然后才能决定箭头往哪儿画、菱形框放在什么位置。
这篇内容我打算按“先看懂框 → 再拆解语义 → 再逐个给案例 → 最后讲踩过的坑”这个顺序来写。无论你是刚接触循环的新手,还是被毕业设计流程图卡住的同学,或者是需要给团队画清楚业务循环的开发者,都能直接从里面挑能用的部分抄走。我会把每个案例的节点、连线、判断条件都写清楚,画图之前先把逻辑推一遍,图自然就出来了。
1.1 循环流程图真正要解决的是什么问题
很多人以为画循环流程图就是“把代码翻译成图形”,其实不完全对。流程图要表达的是控制流的走向,而循环的核心控制流只有一句话:从哪进来、判断什么、什么时候退出去、退出去之后去哪儿。
把这四件事拆开看,循环图就只有三个必备要素。第一是入口,也就是循环变量的初始化放在哪里;第二是判断节点,它是菱形框,负责决定“继续循环”还是“跳出循环”;第三是回流线,也就是循环体执行完之后,箭头往哪根线回去。这三种循环的差别,换句话说,只体现在这三个要素的排列顺序上。
一旦你这么理解,画图就不再是靠记忆去背模板,而是靠推理。你只要问自己三个问题:这个循环的判断是在循环体之前还是之后?变量更新写在循环体里面还是单独拉出来一个框?回流线的终点是判断框还是循环体入口?三个问题答完,图就定型了,而且不会画错。
1.2 三种循环的本质差异:先判断还是先执行
这是所有教材都会讲、但很多同学记不牢的一点。while 和 for 属于先判断后执行,条件不成立时循环体一次都不跑;do while 属于先执行后判断,循环体至少跑一次。这个差别在流程图上的表现非常直接:while 和 for 的菱形判断框在循环体的上方,do while 的菱形判断框在循环体的下方。
我用一个实际场景来说明这个差别为什么重要。比如做一个“输入密码,最多重试三次”的功能,用 while 写的时候,先判断剩余次数是否大于 0,成立才允许输入;用 do while 写的时候,先弹框让用户输入一次,然后再判断是否还有次数。看起来结果差不多,但如果初始条件本来就不满足——比如次数上限被配置成了 0——while 版本一次都不会执行,do while 版本会强行执行一次。这就是“流程图能提前暴露 bug”的典型场景。
| 对比项 | for | while | do while |
|---|---|---|---|
| 判断时机 | 循环体之前 | 循环体之前 | 循环体之后 |
| 循环体最少执行次数 | 0 次 | 0 次 | 1 次 |
| 判断框在图上位置 | 循环体上方 | 循环体上方 | 循环体下方 |
| 初始化框 | 明确独立,通常紧贴判断框上方 | 通常在循环外,可能离得较远 | 通常在循环外 |
| 变量更新位置 | 独立更新框,回流到判断框 | 写在循环体内部 | 写在循环体内部 |
| 回流线终点 | 判断框 | 判断框 | 循环体入口 |
注意:判断“判断框在上还是在下”这个特征时,不要看代码里
while关键字写在第几行,而要看执行时序。有人把 do while 的图照抄成 while 的样子,只把文字换掉,这种图一眼就能被看出来是错的。
2. 画循环图之前,先把流程图的符号规范立起来
画图这事儿最怕的不是逻辑错,而是符号乱。同一个矩形框,你用来表示“处理”,别人以为你在表示“模块”,审图的人直接懵掉。国家有流程图图形符号的通用规范,虽然平时自己画着玩不用那么严格,但只要涉及答辩、评审、交付文档,符号统一就是底线。
我见过最离谱的一张毕业设计流程图,判断框画成圆角矩形,输入框画成正方形,起止框画成菱形,整张图看上去像是把符号打乱重排了一遍。逻辑其实没错,但评审老师第一句话就是“你这个符号用错了”,后面讲什么都不好使了。所以这一章我先把符号讲清楚,再讲连线,最后讲工具怎么选。
2.1 六种核心图形,记住它们各自代表什么动作
流程图符号看起来多,实际常用的就六种,循环图里基本只会用到前五种。
- 起止框:圆角矩形或椭圆形,表示流程的开始和结束。一张图里通常只有一个“开始”和一个“结束”,但如果有多个出口,也可以有多个结束框。
- 处理框:矩形,表示一个具体动作,比如赋值、计算、调用函数。
sum = sum + i就画处理框。 - 判断框:菱形,表示条件判断,一定要有明确的“是/否”两个出口,不能只有一个出口悬空。这是循环图里最关键的框,画错了整张图就没意义了。
- 输入输出框:平行四边形,表示数据的输入或输出。
scanf、printf、读取文件、打印结果,都用它。 - 预处理框或初始化框:六边形,有些规范里用来表示循环变量的初始化或者准备动作,比如
i = 1、sum = 0。如果嫌符号太多,用矩形代替也不会被挑错,但要在一张图里保持一致。 - 连接点:小圆圈,用于跨页或者断线连接。图特别大的时候用,能让画面干净不少,尤其是嵌套循环那种线特别多的图。
提示:判断框里的条件文字尽量写成完整的表达式,比如
i <= 100,而不是只写“循环结束了吗”。审图的人需要看到边界值,边界值恰恰是循环最容易出错的地方。
2.2 回流线是循环图的灵魂,画错一根线全盘皆错
流程图的连线是箭头,方向代表执行顺序。从上往下画直线,从右往左或从下往上画回流线,这是一般的视觉习惯。循环图里最重要的就是那根回流线——从循环体执行完的位置,向上回到判断框的那根箭头。
回流线的终点必须精确。for 和 while 回流到判断框的上方入口,也就是重新去判断条件;do while 回流到循环体入口,也就是重新执行一遍循环体再去判断。这两者画反了,流程图表达的逻辑就完全反了。
还有一个小细节:回流线尽量不要穿过其他框,也不要和别的线重叠。如果画到一半发现线缠在一起了,说明你的图布局有问题,通常的解决办法是把循环体纵向拉长,让回流线走图的右侧或者左侧的空白通道,形成一条干净的回路。嵌套循环更要留出通道,内层的回流线走内圈,外层的走外圈,不要共用一条。
2.3 手绘还是用软件:几种常见工具的取舍
手绘的好处是快,思路还没定型的时候在纸上涂两笔,比开软件快得多。但手绘的图没法直接放进文档,所以最终还是要落到工具上。
| 工具类型 | 代表方式 | 适合场景 | 需要注意的点 |
|---|---|---|---|
| 在线图形工具 | ProcessOn、draw.io | 答辩文档、团队协作、需要精细排版 | 自动布局和手动拖拽要配合,先定结构再调位置 |
| 文本驱动绘图 | mermaid 语法渲染 | 技术文档、代码仓库内的图 | 手动拖动改过的位置不会保存,重新渲染就复原,只适合结构图 |
| 思维导图工具 | XMind 等 | 梳理层级、拆解知识点 | 它的主职是导图,画流程图要靠自由主题加联系线,回流线表达起来别扭 |
| 办公软件 | Word、PPT 的形状工具 | 临时插图、简单流程 | 对齐和对位很费时间,稍微复杂的循环图不要用它硬画 |
| 专业建模工具 | 各类 UML/BPMN 工具 | 系统级设计、流程建模 | 符号体系不同,BPMN 里的网关表达分支合流,不擅长表达“回到上一步” |
我个人的习惯是:逻辑还没定型的时候用文本驱动的画法快速出结构,确认逻辑没问题之后,再用拖拽式工具做最后的精修排版。这样做的好处是,逻辑迭代成本极低,改一行文字图就更新了,不用反复挪框。
3. for 循环的流程图画法:把三个表达式拆成三个框
for 循环是三种循环里结构最规整的,因为它的三个组成部分——初始化、条件、更新——都写在同一个括号里,一目了然。但也正因为它们挤在一行,很多人画图的时候反而不知道该拆成几个框。我的做法是把它们彻底拆开:初始化一个框,条件一个菱形,更新一个框,循环体一个框,四者串起来。
3.1 for 循环的执行时序拆解
以for (i = 1; i <= 100; i++) { sum = sum + i; }为例,它的执行顺序是固定的:
- 执行初始化
i = 1,这一步只执行一次,画在判断框上方。 - 进入判断框,检查
i <= 100是否成立。 - 成立则执行循环体
sum = sum + i,不成立则直接跳到结束框。 - 循环体执行完,执行更新
i++。 - 更新完回到判断框,重新判断。
这里最容易画错的地方是第 4 步和第 5 步的连线。更新框的出口必须连回判断框,而不是连回循环体。如果连回循环体,逻辑就变成了“更新一次然后无限循环体”,条件永远不会重新检查,那就是死循环。另一点是初始化只在最上面执行一次,绝对不能出现在回流路径上。
节点顺序理清之后,参数也好算了。这个循环的判断条件i <= 100,初始值 1,每次加 1,一共要判断 101 次(最后一次判断不成立),循环体执行 100 次。把这个次数写在图旁边的注释里,是审图人特别喜欢的细节,说明你真的推过边界。
3.2 案例:1 到 100 累加的完整流程图描述
直接给节点表,你照着摆框就行。
| 序号 | 节点内容 | 图形 | 出线方向 |
|---|---|---|---|
| 1 | 开始 | 起止框 | 向下 |
| 2 | sum = 0,i = 1 | 处理框(初始化) | 向下 |
| 3 | i <= 100 | 判断框 | 是向下,否向右下到结束 |
| 4 | sum = sum + i | 处理框(循环体) | 向下 |
| 5 | i = i + 1 | 处理框(更新) | 向上回流到节点 3 |
| 6 | 输出 sum | 输入输出框 | 向下 |
| 7 | 结束 | 起止框 | 无 |
判断框的“否”分支走出去之后,是先输出 sum 再结束,这个顺序不能颠倒。有人把输出框画在判断框的“是”分支上,那逻辑就变成每累加一次就打印一次,完全变味了。
3.3 嵌套 for:九九乘法表的图怎么画才不打架
嵌套循环的流程图是重灾区,主要问题就是线缠在一起。拿九九乘法表来说,外层i从 1 到 9,内层j从 1 到i,循环体输出j × i = 结果。它有两个判断框、两个更新框、一条内回流线、一条外回流线。
画法上有个诀窍:把内层循环整体当成外层循环体里的一个大处理块来看待。具体操作是,先把外层结构画出来——外层初始化、外层判断、外层更新、外回流,中间空出一大块当作“外层循环体”。然后在这个空白区域里,再画完整的内层循环小结构,包括内层初始化、内层判断、内层循环体、内层更新、内回流。这样两层结构在视觉上是套娃关系,不会交叉。
具体到九九乘法表,内层初始化j = 1必须写在外层循环体内部,也就是说外层每转一圈,内层变量都要重新初始化一次。这是嵌套循环最经典的坑:如果j = 1画在了外层循环的外面,那内层从第二圈开始就永远不会执行,因为j早就超过i了,判断直接为否。图上一眼就能看出这个错误——初始化框的位置跑到了外层判断框的上方。
4. while 循环的流程图画法:判断框永远在循环体上面
while 循环和 for 的图形结构几乎一样,判断框也在上面,区别在于它没有独立的初始化和更新框,这两件事一个放在循环前,一个写在循环体里。正是因为“更新”被藏进了循环体,while 循环在图上反而更容易暴露强迫症级别的问题。
4.1 更新语句藏在循环体里的后果
写 for 的时候,更新是括号里的一部分,你不会忘;写 while 的时候,更新要自己写进大括号里,一不留神就漏了。而流程图对这件事的呈现方式极其残酷:如果循环体里没有更新框,那从循环体出来的回流线会直接回到判断框,而循环变量从头到尾没变过,判断结果永远相同——图上就是一条纯粹的闭环,没有任何能改变判断结果的动作。
所以画 while 循环图的时候,我强制自己遵守一条规则:循环体的最后一个框,必须是能让判断条件发生变化的框。这不一定是i++,也可能是读入新数据、修改标志位、或者推进指针。如果找不出这样的框,那这个循环要么写错了,要么它本来就是个故意的死循环(比如服务主循环),后者必须在图上用注释明确写清楚。
4.2 案例:密码最多重试三次的 while 版本
需求是:允许用户最多输入三次密码,输对就进系统,三次都错就锁定。用 while 写,逻辑是“只要还有次数并且还没输对,就继续输入”。
| 序号 | 节点内容 | 图形 | 出线方向 |
|---|---|---|---|
| 1 | 开始 | 起止框 | 向下 |
| 2 | count = 0 | 处理框 | 向下 |
| 3 | 输入 password | 输入输出框 | 向下 |
| 4 | count < 3 且 password 不正确 | 判断框 | 是向下,否向右 |
| 5 | 提示密码错误,count = count + 1 | 处理框 | 向下回流到节点 4 |
| 6 | password 正确? | 判断框 | 是向下提示成功,否向下提示锁定 |
| 7 | 输出结果 | 输入输出框 | 向下 |
| 8 | 结束 | 起止框 | 无 |
这张图值得说的是判断条件的写法。如果把“输入密码”放在判断框下面,就变成了先判断次数再输入,那么用户第一次进入时 count 是 0,判断成立,然后才输入——逻辑也通,但判断框里必须写“count < 3”,输入动作在循环体里。两种写法都对,区别在于输入动作发生在判断前还是判断后,画图时选一种并保持一致。
4.3 案例:统计一个整数的位数
再给一个纯数值的案例,帮助理解“更新动作”在图上长什么样。需求是输入一个正整数,输出它是几位数。思路是不断除以 10,每除一次计数加一,直到数变成 0。
- 初始化:
n = 输入值,count = 0 - 判断:
n > 0是否成立 - 循环体:
n = n / 10(整数除法),count = count + 1 - 回流:回到判断框
- 结束:输出 count
这里的更新动作是n = n / 10,它就是那个让判断条件最终变成“否”的关键框。如果这个框漏了,n永远大于 0,图上的闭环就是一个死循环。顺带算一下:输入 12345,除 5 次变成 0,输出 5,边界值 0 输入时 count 为 0,输出 0,这也是为什么用 while 而不是 do while——0 应该输出“0 位”还是“1 位”,取决于业务定义,用 while 至少不会强行执行一次。
5. do while 的流程图画法:判断框挪到最下面
do while 的图,和 while 只差一个位置,但语义差别是本质的。它的菱形框画在循环体下方,从循环体出来先判断,判断为“是”就回流到循环体入口,判断为“否”才走到结束。
5.1 回流线的终点是循环体入口,不是判断框
这是 do while 图最容易画错的一点,我再强调一次。while 的回流线终点是判断框,因为要先判断;do while 的回流线终点是循环体入口,因为它的语义是“再执行一遍循环体”。
判断框的两个出口也容易标反。菱形里的条件是“是否继续循环”,那么“是”应该连回流线回到循环体,“否”才向下走结束。有些同学习惯了 while 的标法,把“否”当成跳出,结果 in do while 里标成“否”回流、“是”结束,整个逻辑就反了——变成条件成立时退出,不成立时循环,跟代码完全对不上。
5.2 案例:菜单程序必须至少显示一次
do while 最典型的应用场景就是菜单。用户打开程序,菜单必须显示出来,然后用户选择操作,选完再问要不要继续。不可能出现“先判断要不要显示菜单,结果一次都没显示”的情况。
节点顺序是这样的:开始 → 显示菜单 → 读取用户选择 → 执行对应功能 → 判断“是否继续” → 是则回流到显示菜单,否则向下到结束。这里“显示菜单”既是循环体的第一步,也是回流线的终点。画的时候把回流线从判断框拉回到“显示菜单”框的上方入口,走图左侧的空白通道,会很干净。
5.3 案例:单片机广告灯左右移控制的循环结构
在嵌入式场景里,比如用单片机做广告灯的左移右移控制,循环结构用得很频繁。常见的做法是:初始化端口和方向变量,然后用循环控制移位的次数,每次移位后加一段延时,移完一轮再反向。
这里的循环画成流程图时,循环体里通常包含三个动作:输出当前灯状态、延时、更新移位变量。判断框放在循环体下方,判断“是否已移完 8 位”。如果用 for 写,判断框就在上面,两者表达的执行次数一样,但 do while 的版本保证了“至少先亮一次灯再谈判断”,这在硬件场景里往往更符合直觉。
注意:嵌入式的循环图里,延时框不能省。很多新手画的图只有“移位”没有“延时”,审图的人一看就知道你根本没跑过硬件——没有延时,灯的移动速度快到肉眼看不见,效果就是全亮。
6. 三种循环放在一起:什么时候用哪个
画图之前先选对循环类型,比画得快更重要。选错了,图要么别扭,要么表达不了真实需求。我一般按下面的思路判断。
6.1 三条互转规则
第一,如果循环次数提前就能确定,比如“累加 1 到 100”“遍历数组的 10 个元素”,选 for,判断条件里天然带着计数器。
第二,如果循环次数不确定,取决于运行时的数据,比如“读到文件结尾为止”“用户输入 0 就停”,选 while,初始化拿出来,更新写进循环体。
第三,如果至少要做一次,比如菜单、先执行再确认的交互,选 do while。
这三种结构在逻辑上是可以互相转换的,这是画图和读图时的重要基本功。for 可以拆成一个初始化框加一个 while;while 可以包装成一个 do while 外加一次前置判断;do while 也可以在前面补一次相同的操作转成 while,只是代码会重复。能在图上把这种转换画出来,说明你对循环的理解已经到位了。
| 判断依据 | 推荐结构 | 图上特征 |
|---|---|---|
| 次数已知、有明确计数器 | for | 判断框上方有初始化框,旁边有更新框 |
| 次数未知、依赖运行时数据 | while | 初始化离得较远,更新在循环体内 |
| 至少执行一次 | do while | 判断框在循环体下方,回流线指向循环体入口 |
| 遍历集合元素 | for 或增强 for | 判断条件写成“还有下一个元素吗” |
| 需要先做再加条件 | do while | 循环体在判断之前 |
6.2 集合遍历类循环的图画法
很多语言里有增强 for 或者 for-each 结构,用来遍历集合、数组、字典。这类循环在流程图上和普通 for 有区别:它的判断条件不是数值比较,而是“是否还有下一个元素”;它的更新动作不是自增,而是“取下一个元素”。
所以画这类图的时候,我会把判断框写成“还有未处理的元素?”,把更新框写成“取出下一个元素”。循环体里则直接用“当前元素”做操作。这样画的好处是,图跟语言的语法细节解耦了,不管你是写 Python 的 for 语句、shell 的 for 遍历列表、还是 JavaScript 的数组遍历,图都是同一张,思路完全一致。
7. 画循环图时最容易踩的坑,以及怎么排查
前面讲的都是怎么画对,这一章讲画错了怎么找出来。循环图的错误有一个特点:逻辑错了,但图形上看起来可能完全正常。一根线画歪一点,整个循环的含义就变了,而肉眼很难第一时间发现。
7.1 死循环在图上长什么样
死循环在流程图上有三种典型形态。第一种,回流线上没有任何能改变判断条件的框,判断框的输入和输出形成了纯闭环;第二种,判断条件写成了恒真表达式,比如1 == 1或者条件里用了赋值符号;第三种,更新框的方向写错了,比如自增写成了自减,导致离目标值越来越远。
排查方法很简单,我习惯用“追踪法”:拿一支笔从判断框出发,沿着“是”分支走一圈,看回到判断框时,判断条件里涉及的变量值有没有发生变化。如果一圈走完变量值一模一样,那就是死循环,图上一定有问题。
7.2 边界值自查清单
循环出错的另一大类是边界,也就是俗称的差一错误。画完图之后,我建议强制自己做一遍边界测试,把结果写在图的旁边或注释里。
- 初始值是否满足判断条件?不满足的话循环体执行几次?
- 最后一次成立时,循环变量的值是多少?更新之后是否刚好让条件为假?
- 循环体内部有没有用到数组下标?最大下标和边界值是否对齐?
- 累加、累乘这类操作,单位元是否初始化正确?累加要设 0,累乘要设 1。
- 嵌套循环的内层变量,是否每轮都重新初始化了?
这五条我基本上每次画循环图都会过一遍,能挡掉九成以上的低级错误。
7.3 工具相关的常见问题速查
画图的工具问题也很消耗时间,这里整理一张速查表。
| 问题现象 | 可能原因 | 处理方式 |
|---|---|---|
| 文本驱动画的图,手动拖动位置后一刷新又乱了 | 渲染是按文本结构重新计算的,手改的位置不持久 | 结构用文本画,精修换拖拽式工具 |
| 回流线和别的线重叠成一团 | 布局没留通道,循环体太扁 | 纵向拉长循环体,回流线走外侧通道 |
| 导出后图里的字被裁掉 | 画布尺寸不够,或字体不兼容 | 导出前留足边距,尽量用通用字体 |
| 思维导图工具画出的循环图很别扭 | 导图工具擅长层级,不擅长闭环 | 导图只用来梳理结构,流程图换专用工具 |
| 系统级流程图里画了循环,评审说不对 | 建模规范里的分支合流符号不表达回到上一步 | 循环用明确的回流线或循环标记表达,别硬套网关符号 |
8. 把循环嵌进系统级流程图里,才是真正能交付的图
单个循环的图练熟之后,下一步就是把它放进真实的系统里。毕业设计、软著文档、需求说明书里的流程图,很少只有孤零零一个循环,通常是几十个框、好几条分支、多个循环嵌套在一起。这时候,“循环画得对不对”已经退居其次,“整张图读不读得懂”才是关键。
8.1 用户管理模块里的循环节点
拿用户管理模块举例,登录部分用到的就是前面讲的密码重试循环;用户列表展示部分用到的就是分页遍历循环——只要还有下一页,就继续拉取数据。这两类循环在系统图里通常不需要展开画到最细,可以把“密码校验重试”打包成一个处理框,在旁边用一个小图或者注释说明它内部的循环结构。
我的做法是分两级画:主流程图只显示模块级的框和箭头,“密码重试循环”画成一个矩形框;然后单独出一张图,把这块的内部循环展开,标上前面讲的判断框和回流线。这样评审的时候,主图清爽,细节图也有,两边都能交代清楚。图书馆管理系统的借阅、归还、超期提醒这些功能,同样是这个套路,循环逻辑单独成图。
8.2 业务建模图里的分支和循环不是一回事
有些同学把循环画到业务建模图里,用网关符号去表达“回到上一步”,结果评审说看不懂。这里要区分清楚:分支类符号表达的是“条件不同,走不同路径,最后合并”,它描述的是同时存在多条路径;循环描述的是同一个动作重复执行。两者不是一回事。
如果非要在业务建模图里表达循环,正规做法是给活动节点加上循环标记,或者单独画一张子图说明这个节点内部是个循环。硬用分支符号去绕,图会变得又乱又难懂,典型的费力不讨好。
8.3 交付前把图过一遍的实操清单
图纸交付前,我一般会做几件事。先通读一遍,从开始框出发,手动走完每条路径,尤其确认每个判断框的两个出口都有终点,没有悬空的箭头。然后检查所有的循环是否都有退出路径,没有的话是否有明确注释说明这是有意为之。
接着看符号是否统一,同一张图里不能一会儿用圆角矩形当处理框,一会儿用矩形当起止框。最后检查文字,判断框里的条件要具体到变量名和边界值,处理框里的动作用动词开头,输入输出框写清楚输入的是什么、输出的是什么。
我在实际带新人时发现,图上的问题往往和代码里的 bug 是同一批问题。能在一张图上把循环画清楚的人,代码里也很少出现死循环和差一错误。这个习惯养成之后,画图就不再是负担,而是排查问题的手段——看着图想逻辑,比盯着代码一行行猜快得多。
这个套路后续还可以往两个方向扩展:一个是把循环和递归放在一起对比着画,看看同一个问题两种结构在图上差在哪;另一个是把循环图直接对应成测试用例,每一个边界值就是一条测试路径,图上的路径走通了,用例也就设计完了。