从基础到中级:WHILE 和 DO WHILE 语句·进阶篇
「WHILE 循环的终止陷阱与 ESC 退出口」
WHILE 和 DO-WHILE 的核心差别只有一句:WHILE 可能一次都不进,DO-WHILE 至少跑一次。逻辑上和 IF 同构——表达式非零才执行,零当作 false 断循环。多数新手甚至老手翻车,原因就一个:忘了在循环体里改终止条件,导致死等。 下面这段是 WHILE 的最简示范。变量 info 初始 10,循环里先减 1 再打印,所以终端看到的是 9 到 0,到零自然停。 [CODE] // 代码 03 对应片段 void OnStart(void) { char info = 10; Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); while (info) { info = info - 1; Print(__FUNCTION__, " : ", info); } Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); } [/CODE] 逐行拆:06 行 char info=10 设初值;10 行 while(info) 判断非零;12 行 info=info-1 递减;13 行打印。零触发终止,因为 char 类型里 0 即布尔假。 若把 12 行改成 info=info+1,并不会真无限——char 有上下限(通常 -128~127),溢出回绕终会归零,但耗时不可控,实盘脚本里绝不能这么写。 MQL5 是事件驱动,纯 WHILE 等事件不常见。下面代码用 ESC 键做紧急出口:循环前只提示一次,按 ESC 通过 TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE) 退出。 [CODE] // 代码 04 对应片段 void OnStart(void) { char info = 5; Print("Press ESC to finish counting..."); while (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) { info = info - 1; Sleep(200); } } [/CODE] 逐行拆:08 行打印一次提示;10 行 while(!ESC) 表示未按 ESC 就持续;12 行递减;13 行 Sleep(200) 让出 CPU。info 变 0 后循环仍不结束,控制权在 ESC 事件。把第 10 行换成 while(!IsStopped()) 还能增加“脚本被图表移除”这一终止路径。 提示只打在循环外,用户错过就不知 ESC 能停。把这个提示移进循环可解决,但正好用 DO-WHILE 更干净——下节展开。外汇与贵金属 EA 开发中误用死循环会卡死终端,属高风险操作,务必加退出保险。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart(class="type">void) { class="type">char info = class="num">10; Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); class="kw">while (info) { info = info - class="num">1; Print(__FUNCTION__, " : ", info); } Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart(class="type">void) { class="type">char info = class="num">5; Print("Press ESC to finish counting..."); class="kw">while (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) { info = info - class="num">1; Sleep(class="num">200);
◍ 用转义键打断死循环
在 MT5 脚本里写 while 轮询时,若条件只依赖指标计算或时间等待,很容易卡成死循环拖死终端。上面这段片段把 TERMINAL_KEYSTATE_ESCAPE 和 IsStopped() 同时作为退出条件,就是给循环留了人工刹车。 TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE) 读取的是键盘 ESC 的实时状态,按一下 ESC 该值变 true,循环即停;IsStopped() 则在用户点停止脚本或终端卸载 EA 时返回 true。两者用 && 取反做继续条件,意味着任一触发就退出。 实盘或回测里验证很简单:把这段塞进一个 OnStart 里的空转计数循环,跑起来后按 ESC,专家日志会停在当前计数而不是一直刷屏。外汇和贵金属行情跳动快,这种强制退出机制能避免脚本占用主线程导致报价延迟,属于高风险环境下的基础防护。
class="kw">while ((!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) && (!IsStopped()))
DO-WHILE 至少跑一次的底气
WHILE 循环只在条件为真时才进体,DO-WHILE 反着来:DO 块先执行,再去判断 WHILE 条件。所以哪怕初始条件为假,循环体也必然跑至少一遍。写行情巡检、按键等待这类「先出一次信号再谈退出」的逻辑时,这个特性比纯 WHILE 更顺手。 下面这段是典型写法:info 从 10 开始,每轮先判断是否整除 5,再自减、Sleep 200 毫秒、打印,直到 ESC 被按下才停。注意第 8 行 do 和第 14 行 while 成对,编译器才认得循环边界在哪。 [CODE]
- //+------------------------------------------------------------------+
- #property copyright "Daniel Jose"
- //+------------------------------------------------------------------+
- void OnStart(void)
- {
- char info = 10;
07.
- do
- {
- if (((info / 5) * 5) == info) Print("Press ESC to finish counting...");
- info = info - 1;
- Sleep(200);
- Print(__FUNCTION__, " : ", info);
- }while (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE));
15.
- Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway...");
- }
- //+------------------------------------------------------------------+
逐行拆:06 声明 char info=10;08 do 标记循环体起点;10 用 (info/5)*5==info 判断整除,成立才打印提示;11 info 减 1;12 暂停 200ms 避免刷屏;13 打印函数名和当前值;14 while 检测 ESC 状态,没按就继续;16 退出循环后必执行的一句。 别把 do 随手删了。若像代码 06 那样去掉第 8 行 do,第 9–13 行就退化成普通顺序代码,只跑一次,然后第 14 行变成空壳 while 死等 ESC——本该在体里发生的自减和退出判断再也不会触发,无限循环就来了。 第 10 行那个「除以 5 乘回 5」看着多余,其实在强类型里能暴露整除截断:char 是整数,info=10、5 时相等会打印,info=9 时 9/5=1、1*5=5≠9 就不打印。想复现间隔提示,除法乘法必须用同一个整数,且先除后乘;浮点在这儿会失效,别换 double 试。
class="num">01. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">02. class="macro">#class="kw">property copyright "Daniel Jose" class="num">03. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">04. class="type">void OnStart(class="type">void) class="num">05. { class="num">06. class="type">char info = class="num">10; class="num">07. class="num">08. do class="num">09. { class="num">10. if (((info / class="num">5) * class="num">5) == info) Print("Press ESC to finish counting..."); class="num">11. info = info - class="num">1; class="num">12. Sleep(class="num">200); class="num">13. Print(__FUNCTION__, " : ", info); class="num">14. }class="kw">while (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)); class="num">15. class="num">16. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); class="num">17. } class="num">18. class=class="str">"cmt">//+------------------------------------------------------------------+
「正文」
<span class="number">14</span>. }<span class="keyword">while</span> (!<span class="functions">TerminalInfoInteger</span>(<span class="macro">TERMINAL_KEYSTATE_ESCAPE</span>)); <span class="number">15</span>. <span class="number">16</span>. <span class="functions">Print</span>(<span class="keyword">__FUNCTION__</span>, <span class="string">" "</span>, <span class="keyword">__LINE__</span>, <span class="string">" It will be executed anyway..."</span>); <span class="number">17</span>. } <span class="number">18</span>. <span class="comment">//+------------------------------------------------------------------+</span> [/CODE]
◍ 把工具请下神坛
循环在 MQL5 里不是什么高深咒语,它只是把重复劳动交给 CPU 的朴素手段。前面演示的阶乘例子,用 while 跑 5 次乘法就得出 120,若换最初那种手写连续调用,逻辑散开且易抄错行号。 上面这段脚本里,counter 从 0 走到 4,value 依次乘 1、2、3、4、5,第 15 行 Print 出来正是 Factorial of 5 => 120。你可以直接贴进 MT5 的脚本文件里跑一遍验证。 外汇与贵金属杠杆高、滑点狠,自动化逻辑再优雅也挡不住宏观跳空,动手改参数前先想清楚回测样本够不够。下一篇会接着拆循环没讲完的坑,现在开编辑器把第 6 到 10 行那段顺序调用重写成循环,比看十篇闲谈都管用。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart(class="type">void) { class="type">uchar counter = class="num">0; class="type">class="kw">ulong value = class="num">1; class="kw">while (counter < class="num">5) { counter = counter + class="num">1; value = value * counter; } Print("Factorial of ", counter, " => ", value); } class=class="str">"cmt">//+------------------------------------------------------------------+