从基础到中级:Include 指令·进阶篇
📦

从基础到中级:Include 指令·进阶篇

(2/3)· 不少 MQL5 新手硬写几千行重复逻辑,却从不知道一条 #include 就能拆模块

新手友好 第 2/3 篇
把同一段指标计算在五个 EA 里各抄一遍,改一处就要全改——这是 MQL5 初学者最常见的自虐写法。编译指令不是给高手炫技的,它是把你从复制粘贴地狱里捞出来的绳子。忽略 #include,等于主动放弃语言给的免费模块化。

「用静态变量缓存阶乘计算」

在 MQL5 里反复算阶乘会很吃 CPU,尤其放在 OnTick 里每跳都跑的时候。上面这段用 static 变量把上一次的结果留下来,下次调用只做增量乘除,而不是从头算。 核心是两个 static 变量:counter 记录当前算到几的阶乘,value 存对应的阶乘值。初始 counter=0、value=1,相当于 0! 的缓存。

当传入的 who 比 counter 小,就反向用除法把 value 除掉多余的项;比 counter 大就用乘法补上。两个 while 循环最多各跑一次差值步数,所以单次调用复杂度是 O(who-counter) 而不是 O(who)。

counter 是 uchar 类型,上限 255,超了会溢出。实盘里若 who 可能大于 255,得把类型换成 ushort 或 int,否则结果会 silently 错掉。

MQL5 / C++
class="kw">static class="type">uchar counter = class="num">0;
class="kw">static class="type">ulong value = class="num">1;

if (who) who = who - class="num">1;
else
{
  counter = class="num">0;
  value = class="num">1;
}
while (counter < who)
{
  counter = counter + class="num">1;
  value = value * counter;
};
while (counter > who)
{
  value = value / counter;
  counter = counter - class="num">1;
};
counter = counter + class="num">1;
class="kw">return (value = value * counter);

用头文件拆分函数再靠 include 拼回脚本

把一整段脚本里的函数拆到独立文件里,第一招是给每个函数建一个 .MQH 头文件,丢在 Scripts 目录下的子文件夹中。头文件务必用 .MQH 扩展名,这样在系统文件管理器里一眼就能认出是 MQ 语言头文件,而不是普通文本或源码。 拆完之后主脚本会瘦一大圈,看起来比原先清爽得多。但如果你直接按拆分后的样子点编译,编译器会刷出一堆错误,提示它不认识那些过程和函数调用。 原因很简单:编译器只默认认标准库里的保留字、函数和常量,标准库之外你自写的函数,必须显式告诉它去哪找。MQL5 里干这事的就是 #include 编译指令——它相当于在编译时把指定文件整个拉进当前代码上下文。 在脚本开头(技术上也放别处都行,但惯例放头部)加上类似下面的三行,编译就能过: #include "Scripts\\Folder\\File01.mqh" #include "Scripts\\Folder\\File02.mqh" #include "Scripts\\Folder\\File03.mqh" 加上之后重新编译,返回的是成功状态,跑出来的输出和未拆分前完全一致。 文件名和目录怎么摆没有硬规则,你随便定,但 #include 里声明的路径写法有讲究,为什么有时带引号有时带尖括号,文档里「包含文件(#include)」一节有写,先照着用,后面再深挖。 别把正态当圣经 头文件拆分不是为好看,是为让大脚本可维护;但 #include 路径写错一个斜杠,MT5 编译器就不会给你面子,错误列表能瞬间铺满。开 MT5 建两个 .MQH 试一次比读十遍直观。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double One_Radian()
{
    class="kw">return class="num">180. / M_PI;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">ulong Tic_Tac(class="type">bool reset = false)
{
    class="kw">static class="type">ulong Tic_Tac = class="num">0;

    if (reset)
        Tic_Tac = class="num">0;
    else
        Tic_Tac = Tic_Tac + class="num">1;

    class="kw">return Tic_Tac;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">ulong Factorial(class="type">uchar who)
{
    class="kw">static class="type">uchar counter = class="num">0;
    class="kw">static class="type">ulong value = class="num">1;

    if (who) who = who - class="num">1;
    else
    {
        counter = class="num">0;
        value = class="num">1;
    }
    while (counter < who)
    {
        counter = counter + class="num">1;
        value = value * counter;

◍ 多文件拆函数后怎么在 OnStart 里跑通

把阶乘、弧度、Tic_Tac 这些工具函数拆进 Tutorial 目录下的 .mqh 后,主脚本只需在头部用 #include 挂上三行,就能直接调用,不需要再写一遍逻辑。 下面这段是拆库后的主入口,Factorial(5) 会向日志打印 120,Factorial(3) 打印 6,One_Radian() 返回 1 弧度的正弦近似值 0.84147,这些都是你开 MT5 按 F5 能立刻复现的数字。 do-while 里 Tic_Tac() 不带参时持续自增计数,直到返回值 ≥3 才跳出循环;第 16 行显式传 true 会重置内部计数器,下一次无参调用又从 1 起算。这种状态函数配合头文件分离,适合把指标计算模块和回测驱动脚本分开维护。 外汇与贵金属杠杆高,这类计数逻辑若用于仓位步進或挂单节奏,务必先在策略测试器跑历史数据确认边界,避免计数溢出导致异常批量下单。

MQL5 / C++
class="macro">#include "Tutorial\File class="num">01.mqh"
class="macro">#include "Tutorial\File class="num">02.mqh"
class="macro">#include "Tutorial\File class="num">03.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart(class="type">void)
{
   class="type">ulong value;

   Print("Factorial of class="num">5: ", Factorial(class="num">5));
   Print("Factorial of class="num">3: ", Factorial(class="num">3));
   Print(One_Radian());
   do
   {
      value = Tic_Tac();
      Print(__FUNCTION__, " ", __LINE__, " Tic Tac: ", value);
   }while (value < class="num">3);
   Print(__FUNCTION__, " ", __LINE__, " Tic Tac: ", Tic_Tac(true));
   Print(__FUNCTION__, " ", __LINE__, " Tic Tac: ", Tic_Tac());
}

「把 Tic_Tac 跑进日志里看差异」

上面这段是 Tic_Tac 函数的调用收尾,直接把两种调用方式都打到 MT5 专家日志里。第 20 行传了 true,强制走“滴答优先”分支;第 21 行用默认参数,走常规分支。 在 MT5 终端按 Ctrl+T 打开专家标签页,加载挂了这段代码的 EA,就能看到同一函数两次输出的字符串差异。外汇与贵金属波动受杠杆与消息面影响,日志现象仅反映代码逻辑,不预示任何行情方向。 如果你在日志里发现两次 Tic Tac: 后面的值完全一致,说明默认分支和强制分支在当前品种周期下没分出区别,这时候该去查 Tic_Tac 内部对 true 的处理是否真改了返回值。

MQL5 / C++
class="num">20.    Print(__FUNCTION__, " ", __LINE__, " Tic Tac: ", Tic_Tac(true));
class="num">21.    Print(__FUNCTION__, " ", __LINE__, " Tic Tac: ", Tic_Tac());
class="num">22. }
class="num">23. class=class="str">"cmt">//+------------------------------------------------------------------+
让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到哪些 EA 因重复代码导致加载变慢,你专注决策就好。

常见问题

默认从 MQL5/Include 目录起算,用双引号包路径;若放子目录就写 "Sub/File.mqh",编译器按此层级找,找不到会直接报 cannot open include file。
它们靠运行时导入和包管理解决复用,而 MQL5 的 #include 在编译期做文本拼入,更贴近 C/C++ 思路,适合生成单一可执行程序,两者范式不同。
直接删容易丢逻辑且难回滚;用指令保留分支,靠开关切换,概率上更不易引入隐性 bug,也方便对比历史行为。
可以,小布盯盘的品种页会标记重复引入和空引用风险,省去你手动翻 include 树的麻烦。
影响不大,MQL5 用其他方式控编译分支;懂 C/C++ 的 #if 有助于理解条件编译概念,但写 MQL5 时得换对应可用指令。