Mixly图形化系列教程写到第七篇了。前面六篇把数字输入输出、模拟量读取、串口通信、分支判断和for循环挨个过了一遍,到这个节点,还在继续看的朋友,基本都是真的在拿Mixly做项目的人了。这篇接着循环往下走,目标很明确:把while和do...while这两个循环结构一次性讲透。用关键词“while”去搜,网上大多是被各种报错刷屏,但在图形化编程里,while不报错,它只负责一件事——让程序在某个条件还没满足的时候,老老实实待在一个地方干活。这篇适合三类人:带学生上创客课程的老师、自学没入门多久的中小学生,以及学完for循环想把它和C语言对应起来的零基础爱好者。看完你不但知道这两个积木在哪,还能直接用它们做出按键控制、掷骰子这类小项目,并且彻底搞清楚它俩到底差在哪。
1. 先搞清楚三种循环到底在解决什么问题
1.1 for、while、do...while其实是三种“循环姿势”
很多初学者一开始接触for循环,觉得“循环就是规定次数,跑完拉倒”。这个印象不能说错,但容易把思路框死。等做到实际项目就会发现,很多时候程序根本不知道要循环几次,只知道“什么时候该停”。这时候for循环就不太顺手了。
我上课时喜欢用生活场景做类比:
- for循环像“套餐”——说好吃几个菜就做几个菜,次数是提前定死的。
- while循环像“自动售货机找零”——机器一直往外退硬币,退到余额为0才停,谁也不知道一开始要退几次。
- do...while循环像“先尝后买”——你先吃一颗葡萄,甜就继续买,不甜就不买了,但无论怎样,第一颗葡萄总归是吃过了。
这个“先吃一颗”的细节,恰恰是do...while和while最核心的区别:do...while不管条件成不成立,循环体里的代码至少先跑一次。for循环适合次数明确的场景,比如让LED闪烁10次;while适合条件驱动的场景,比如等待按钮松开;do...while适合“不管三七二十一先做一次再说”的场景,比如先掷一次骰子,再决定要不要继续掷。
把它们放到一块对比,逻辑一下就清晰了:
| 循环类型 | 判断时机 | 最少执行次数 | 典型适用场景 |
|---|---|---|---|
| for | 先判断,次数由循环变量控制 | 0次 | 闪烁N次、遍历一组引脚 |
| while | 先判断,条件满足才进入 | 0次 | 等待按键松开、等待传感器越限 |
| do...while | 后判断,先执行一次循环体 | 1次 | 先采样再判断、先出结果再问是否继续 |
1.2 为什么学完for循环还要再学while
有朋友留言问:既然for循环什么都能干,为什么Mixly里还要单独给while和do...while留两个积木?这个问题问得很到位。
for循环看起来万能,是因为“循环次数”这个信息在写程序时就得确定下来。但Arduino这类硬件项目里,真正的大头是“感知外部世界”:一个按键何时被按下、一个传感器数值何时超过阈值、串口何时收到数据。这些事件发生的时间点完全不可预知,程序只能一遍遍去查、去等,等到条件变化了再行动。
这种情况用for循环硬写也能写,但代码会非常别扭,你得先估算一个最大次数,然后在循环体里不停判断条件是否满足,写出来的东西又长又绕。while循环天生就是干这个的,它把“继续循环的条件”直接摆在明面上,程序读起来跟自然语言差不多。
另一个原因是衔接未来。Mixly是图形化编程,但它的最终目的不是让学生停留在拖积木,而是理解程序逻辑,为将来写代码做准备。Arduino的C语言里,while和do...while是绕不开的语法。在Mixly里先把这两个结构玩熟,以后看到大段的C代码就不会发怵。这也是我一直推荐学生用Mixly右上角“代码”按钮的原因——每拖一个积木,就切到代码区看一眼,图形和代码一一对应,这个习惯越早养成越好。
2. 在Mixly里找到while和do...while积木
2.1 积木在哪、怎么认
Mixly的积木分类和Scratch类似,循环相关的都在“控制”分类里。打开控制分类,往下拉,会看到几个长相接近的循环积木:有“循环次数”(这就是for)、有“重复无限次”,还有一个条件写在最前面的“当...执行”,这就是while循环。
while积木的样子很直观:顶上一个六边形凹槽,用来填判断条件;中间一个可以塞其他积木的凹槽,是循环体;整体看就是“当条件成立的时候,反复执行下面的积木”。学生第一次看到这个积木,我一般会让他们大声读一遍:“当……执行……”——读顺了,逻辑也就懂了一半。
do...while在Mixly里的积木长这样:一段循环体凹槽,底部跟着一个六边形条件判断区,积木上的文字通常是“执行...直到...”这样的结构。注意观察,它的条件判断槽在积木底部,而不是顶部,这个外观差异恰恰对应了“先执行后判断”的执行顺序。上课的时候我让学生看积木就判断循环顺序,他们记得特别牢,因为图形把语义画在脸上了。
另外,控制分类里还有一个“重复无限次”积木,它本质上就是while(1)或者while(true),一个永远满足条件的while循环。初学者有时候会把“重复无限次”和“当...执行”搞混,其实区别很简单:一个是条件永远成立,一个是可以变动的条件判断。
2.2 展开看代码:图形化积木对应的C语言到底长什么样
Mixly最终会生成Arduino C代码。切换到“Arduino C”模式,while积木生成的核心代码是这样的:
while (条件) { // 循环体代码 }do...while积木生成的代码是这样的:
do { // 循环体代码 } while (条件);肉眼可见的区别有两个。第一是条件和循环体的先后顺序;第二是do...while在C语言里必须以分号结尾,很多人手写代码时容易漏掉这个分号,编译直接报错。在Mixly里拖积木不会遇到这种低级错误,但看代码时心里要有数。
还有一点值得注意:Arduino本身的setup()和loop()结构就是一个大循环。loop()函数里的代码会被反复执行,它本质上就是一个永不结束的while循环。理解了这一点,就能明白为什么很多Mixly例程看起来没有“明显”的循环,程序却能一直跑——因为整个loop()就是最大的那个while。这个认知对后面学状态机、学定时器都特别重要。
3. 三个能直接抄作业的实操案例
3.1 按住按键灯闪,松开就停:while的入门体验
第一个案例我用的是一个按键加一个LED,接线非常简单:按键模块接数字7脚,LED接13脚(或者直接用板载LED)。要做的事情是:按住按键不放,LED就一直以0.2秒间隔闪烁;一松手,LED立刻停止,程序继续往下走。
Mixly里的积木拼法是这样的:先拖一个“当...执行”积木,条件填“数字输入 引脚7 == 高电平”,然后在循环体里依次放入“数字输出 13 高电平”“延时200毫秒”“数字输出 13 低电平”“延时200毫秒”。这段程序生成出来的C代码大概是:
while (digitalRead(7) == HIGH) { digitalWrite(13, HIGH); delay(200); digitalWrite(13, LOW); delay(200); }这个案例几乎没有难度,但它把while的核心语义讲透了:按键按下时,条件成立,程序被“困”在循环体里反复闪灯;按键松开那一瞬间,条件变成假,程序立刻脱离循环,继续往后执行。我建议学生把这段程序后面再加一行“串口打印换行(按钮松开)”,然后打开串口监视器观察:松手后才会看到这行字。这个观察过程比任何解释都管用。
实操中有一个坑必须提醒:按键模块分为“按下输出高”和“按下输出低”两种,有的学生把条件写反了,结果变成“按住不闪、松开才闪”。遇到这种情况先别急着改程序,用串口打印一下按键的实时电平,确认它按下时到底是高还是低,再回来改条件。这也是嵌入式开发的基本素养——先确认硬件状态,再怀疑代码逻辑。
3.2 按键按一次、状态翻一次:while的阻塞等待妙用
第二个案例是第一个的升级版,也是实际项目里非常高频的需求:按一下按键,LED亮;再按一下,LED灭。看起来简单,直接写“如果按键按下,就把LED取反”行不行?我一向鼓励学生先自己试,试完十有八九会发现问题:按键按一次,灯经常“跳”好几下,状态完全不受控。
原因很好理解。程序每秒能循环成千上万次,而人手按一次按键的时间是几百毫秒。在这几百毫秒里,条件“按键按下”一直为真,if语句里的取反操作被执行了成千上万次,LED亮灭来回翻转,最后停在哪个状态基本靠运气。这就是典型的“按键抖动/重复触发”问题。
解决办法正是while循环的拿手好戏:检测到按键按下后,先执行一次LED取反,然后让程序卡在一个while循环里,死等按键松开。程序逻辑变成:
if (digitalRead(7) == HIGH) { digitalWrite(13, !digitalRead(13)); while (digitalRead(7) == HIGH) { delay(10); } }对应到Mixly积木:最外层是“如果”积木,条件判断按键按下;如果块里第一件事是设置13号引脚输出为“取反”(Mixly的交互式引脚选择可以直接反选),第二件事就是拖一个“当...执行”积木,条件还是按键按下,循环体里只放一个10毫秒延时。这段程序翻译成人话就是:按键一旦按下,我先把灯翻个面,然后待在这个循环里不动,直到你把手指松开,我才继续干别的。
很多学生不理解为什么循环体里只有一个“延时10毫秒”就能“等待松开”。这里的关键是,只要按键没松开,条件一直为真,while就一直在空转,每转一圈等10毫秒,等到松开了,条件变假,循环自然退出。这个“空转等待”的用法,就是嵌入式里常说的“阻塞式等待”,它牺牲了一点效率,但换来了逻辑上的绝对可靠。中间加延时是为了降低空转频率,顺便起到按键消抖的作用,一举两得。
这个案例我还建议学生做一个扩展实验:在while循环体里放一个计数器变量,每次循环加1。等下松开按键后,把计数器的值打印到串口监视器。学生会惊讶地发现,一次简单的按动,循环体居然转了几十甚至上百次。数值越夸张,他们对“程序跑得比手快”这个事实的印象就越深刻。
3.3 掷骰子直到6点:do...while的必玩案例
第三个案例直接上do...while,因为它展示的“至少执行一次”特性是while替代不了的。项目需求:模拟掷骰子,在串口监视器里不断输出1到6之间的点数,只要没掷出6,就继续掷;一旦出现6,停止程序。
先分析一个问题:这个程序最少会掷几次?答案是1次。因为程序启动之后,无论怎样都要先掷一次骰子,出结果之后再判断要不要继续。这种情况下用while写会很别扭,你得先把变量初始化成一个不等于6的值来“骗过”第一次判断:
int 点数 = 0; while (点数 != 6) { 点数 = random(1, 7); Serial.println(点数); }这个写法能跑,但那个“点数=0”的初始赋值是多余的,纯粹是为了绕过while先判断的特性。而do...while天然匹配这个场景,先掷骰子,后判断,写出来干净利落:
int 点数; do { 点数 = random(1, 7); Serial.println(点数); delay(500); } while (点数 != 6);Mixly积木的拼法:先拖一个“执行...直到”积木,循环体里放“变量赋值(随机整数 从1到6)”、“串口打印换行(变量点数)”、“延时500毫秒”;条件槽里填“变量点数 == 6”。注意积木上的文字是“直到”,意思是“一直做到这个条件满足为止”,所以条件要写“点数等于6”,不要被绕晕。
随机数积木要单独说一下。Mixly的“数学”分类里一般能找到“随机整数”积木,不同版本参数范围略有差异。我用的Mixly 1.x经典版里,积木显示“从1到6”,但生成的C代码其实是random(1,7),因为Arduino的random(a,b)取到的是a到b-1的整数。如果学生用了不同版本的Mixly,参数范围显示可能会不一样,所以我都会提醒一句:拖完积木务必切到代码区看一眼生成结果,确认随机数落点对不对,顺便加深对“左闭右开”区间这个概念的理解,以后写代码能少踩一个坑。
这个案例视觉效果很欢乐,串口监视器里数字不停翻滚,直到蹦出一个6才停。我还会让学生加一个计数器,记录一共掷了多少次才出6,顺便再讨论一个数学问题:掷很多次才出6的概率有多高?编程课顺便上了数学课,学生们反而记得更牢。
4. 常见问题与排查技巧实录
4.1 程序一上传就“死机”,多半是while没有出口
学生在课堂上遇到最多的情况,是程序上传之后板子完全没反应,按按键没动静,串口监视器一片空白,仿佛板子“死机”了。排查来排查去,最后九成都是同一个原因:while循环的条件永远为真,循环体里又没有改变条件的操作,程序卡死在死循环里出不来了。
经典的错误写法比如:循环条件写“1 > 0”(这个条件任何时候都为真),循环体里只放延时,没有任何变量变化。代码层面等价于:
while (1 > 0) { delay(100); }程序一辈子停在这里,后面写的所有代码都成了摆设。还有一种更隐蔽的情况:循环条件依赖某个变量,但循环体里忘了更新这个变量。比如想用while让变量从10减到0,条件写“变量 > 0”,循环体里却不写“变量减1”,结果变量永远是10,条件永远成立,照样死循环。在Mixly里碰到这种情况,我会让学生先在循环体最前面拖一个“串口打印换行”,打印循环计数器,然后打开串口监视器观察,能看到数字一直刷、停不下来,就说明循环没有出口。这个“打印中间变量”的排查思路,是调试程序最基础也最有效的手段。
另外Mixly控制分类里有个“跳出循环”积木,对应C语言的break。它可以在循环体里加一个判断条件,满足时就强制跳出循环。我建议初学者在做while练习时,先放一个“跳出循环”保底,比如计数器超过1000就强制退出,防止程序长时间卡死。等逻辑确认无误了,再把这个保险撤掉。这不是什么高深技巧,但能让你在调试阶段少拔好几次USB线。
4.2 循环次数莫名其妙多了一次?先画流程图
while和do...while的另一个常见现象是“循环次数比预期的多一次”。比如条件写“当i小于5执行”,程序退出循环时i已经变成5了;如果是do...while先执行再判断“直到i不小于5”,有可能循环体已经执行了6次。这个差异不是写错了,而是两种结构的天然区别。
学生容易在这个地方绕晕,我一般让他们拿出纸笔,把循环画成流程图。while的流程是:先到菱形判断框看一眼,条件不过就绕过去;do...while的流程是:先冲进方框执行一遍,再回来到菱形判断框。流程图画完,多出来的那一次在哪一步增加的,一目了然。
还有一种边界条件导致的多跑一次:判断条件用了大于号还是大于等于号,结果完全不同。比如要做“倒计时从10到1”,条件写成“i > 0”会执行到i变成0退出,条件写成“i >= 0”则会多执行一次到i变成-1才退出。图形化编程虽然不涉及复杂的语法符号,但比较积木里的“大于”和“大于等于”是两个不同的积木,拖的时候一定要想清楚边界。我的习惯是让学生用串口把退出循环后变量的值打印出来,看到数值比预期多1或少1,再去检查条件里的边界选择。
4.3 常见问题速查表
下面这张表是这几年带学生踩坑经验的汇总,遇到类似情况可以直接对照排查:
| 症状 | 可能原因 | 排查方法 | 解决思路 |
|---|---|---|---|
| 程序上传后没任何反应 | while条件永真,死循环 | 循环体加串口打印,看数据是否停住 | 检查条件变量是否被更新,用“跳出循环”兜底 |
| 按键控制灯乱跳 | 没有做防重复触发 | 串口打印按键按下次数 | 用while循环等待按键松开 |
| 条件写反,松开才执行 | 按键模块高/低有效搞混 | 串口打印引脚实时电平 | 确认硬件逻辑后反转条件 |
| do...while比预想多执行一次 | 先执行后判断的语义没理解 | 画流程图,逐次走一遍 | 对比“直到”和“当”两种写法 |
| 随机数出现0或者7 | 随机数上下限理解有误 | 切到代码区查看random参数 | 检查积木范围和“左闭右开”区间 |
| 循环里串口数据刷屏看不清 | 循环体执行过快 | 循环体加延时 | 延时100到500毫秒再观察 |
5. 带过几百个学生后,我总结的循环教学心得
前面说的都是具体操作方法,最后聊几句教学层面的经验。我带学生认识循环,比较常用的顺序是先for、再while、最后do...while。for循环最直观,学生上手快,但它容易让人误以为“循环就是数数”,所以教for的时候一定要埋个伏笔:屏幕上闪烁次数够了就停,那如果我想让它一直闪到按键被按下才停,该用哪个循环?问题抛出去,再引出while,学生就有了方向感。
循环这一部分,我特别建议大家带着学生“故意写一次死循环”。我有个学生,做按键控制LED那个案例时,把while条件写成了“按键没有按下”,结果灯永远不响应按键。后来我们在while循环体里加了串口打印,才发现程序在他手指悬空的那几毫秒里已经跑了很多次,按键按下的那一瞬间反而被跳过了。这种亲身体会比老师讲十遍道理都管用。让学生亲手制造一个bug,再亲手排除它,循环结构里最难理解的几个点(条件判断时机、循环体执行顺序、条件变量更新)就全通了。
do...while这个结构,说实话在日常教学里容易被一带而过,但它对应的“先做一次再问”的思考模型在真实项目里很常见。我先让学生举生活里的例子,回答“什么事情是你至少会做一次,然后才决定要不要继续的”,有学生说“先点一口菜尝好不好吃”,有学生说“先试穿衣服再决定买不买”,这就是对do...while最直观的理解。有了生活经验打底,回来看“执行...直到”积木就不觉得抽象了。
每次上完循环这一课,我都会留一个综合小项目当作业,比如做一个“密码门锁”:从串口输入四个数字,程序逐个校验,错了提示重新输入,对了点亮指示灯。这里面既要用到while做输入轮询,也要用到do...while做“不管怎样都要先尝试一次”的交互流程。学生做完这个项目,对循环的整体把握会比只会做闪烁灯高出不少。这套内容讲下来,学生的逻辑思考能力提升很明显,我们下篇可以接着聊循环里的break和continue,或者直接进入函数,看大家需求来定。