从基础到中级:定义(二)·进阶篇
⚙️

从基础到中级:定义(二)·进阶篇

(2/3)· 还在用函数调用堆叠内存?这篇把#define的第二种玩法拆给你看

新手友好 第 2/3 篇
很多交易者写EA时习惯把重复逻辑包成函数,却没注意调用会带来内存压栈开销。其实MQL5里藏着一个inline保留字,能让宏把代码直接铺平跑得更快。搞懂它,你的回测循环可能少卡几毫秒。

◍ 递归与宏两种斐波那契取法

这段 MT5 脚本同时给了递归函数和宏两种算斐波那契数列第 N 项的写法,默认取第 11 项(def_Fibonacci_Element_Default = 11)。 递归版 Fibonacci_Recursive 在 arg<=1 时直接返回 arg,否则返回前两项递归之和,代码直观但重复计算多;宏 macro_Fibonacci_Interactive 用 for 循环迭代,变量 i、v 交替累加,按 arg 奇偶取 i 或 v 输出。 开 MT5 新建脚本粘入下方代码,编译运行后「专家」标签页会先打印递归结果、再打印宏结果,两者应一致(第 11 项 = 89)。外汇与贵金属交易涉及高杠杆,此类代码仅作算法验证,不构成任何交易信号。 别把递归当生产首选 实盘里若需算较深序列,递归指数级调用栈可能拖慢 MT5;宏迭代 O(N) 更稳,调 def_Fibonacci_Element_Default 改项数即可秒验。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define def_Fibonacci_Element_Default   class="num">11
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart(class="type">void)
{
   Print("Result: ", Fibonacci_Recursive());

class="macro">#define macro_Fibonacci_Interactive      {                               \
         class="type">uint v, i, c, arg = def_Fibonacci_Element_Default;              \
         for (c = class="num">0, i = class="num">0, v = class="num">1; c < arg; i += v, c += class="num">2) v += i;      \
         Print("Result: ",  (c == arg ? i : v));                         \
                                                                    }

   macro_Fibonacci_Interactive;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">uint Fibonacci_Recursive(class="type">uint arg = def_Fibonacci_Element_Default)
{
   if (arg <= class="num">1)
      class="kw">return arg;

   class="kw">return Fibonacci_Recursive(arg - class="num">1) +  Fibonacci_Recursive(arg - class="num">2);
}
class=class="str">"cmt">//+------------------------------------------------------------------+

把参数塞进宏里的几种写法

宏本质是编译期原地展开的代码块,不像函数那样有参数类型检查。代码 05 第 14 行把值当函数参数传,第 18 行拿到结果;而同样逻辑的宏在第 4 行就把值写死了,跑出来结果不一样,这就是宏不能随便当函数用的第一个坑。 想让宏接收外部值,得在 #define 里留占位符。代码 06 改完之后,宏调用处能传独立值,但跑起来和代码 02 差不多——只是把第 15 行代码复制到各处,教学可以,实盘基本没用。 真有点用的是代码 07:第 14 行声明 ulong 变量却不初始化,第 16 行通过宏按引用改掉它。注意第 21 行函数要的是 uint,所以第 16 行做了显式转型避免编译器警告。宏在第 9 行改了值,主程序里立刻看得见——这种「点击即联动」的引用传递,用错就是定时炸弹。 宏也能装成函数。代码 08 第 6 行定义的宏在第 11 行当函数用,目的不是提速,是隐藏复杂度让代码能读。片段 01 里那组 datetime 宏就是典型:代码 09 第 13–21 行连续调用,每个宏返回变量名+值的十六进制和十进制,终端图 06 证明 datetime 取值正确。外汇和贵金属 EA 里这么写能少踩类型坑,但宏无编译器护航,高风险操作前务必在 MT5 策略测试器单跑验证。

MQL5 / C++
<span class="number">class="num">01</span>. <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="number">class="num">02</span>. <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">copyright</span> <span class="class="type">class="kw">string">"Daniel Jose"</span>
<span class="number">class="num">03</span>. <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="number">class="num">04</span>. <span class="preprocessor">class="macro">#define </span>def_Fibonacci_Element_Default&nbsp;&nbsp; <span class="number">class="num">11</span>
<span class="number">class="num">05</span>. <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="number">class="num">06</span>. <span class="preprocessor">class="macro">#define </span>macro_Fibonacci_Interactive&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \
<span class="number">class="num">07</span>.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">uint</span> v, i, c, arg = def_Fibonacci_Element_Default;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\
<span class="number">class="num">08</span>.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">for</span> (c = <span class="number">class="num">0</span>, i = <span class="number">class="num">0</span>, v = <span class="number">class="num">1</span>; c &lt; arg; i += v, c += <span class="number">class="num">2</span>) v += i;&nbsp;&nbsp;\
<span class="number">class="num">09</span>.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Result: "</span>,&nbsp;&nbsp;(c == arg ? i : v));&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \
<span class="number">class="num">10</span>.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
<span class="number">class="num">11</span>. <span class="comment">class=class="str">"cmt">//+----------------+</span>
<span class="number">class="num">12</span>. <span class="keyword">class="type">void</span> <span class="functions">OnStart</span>(<span class="keyword">class="type">void</span>)
<span class="number">class="num">13</span>. {
<span class="number">class="num">14</span>.&nbsp;&nbsp;&nbsp;&nbsp; <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Result: "</span>, Fibonacci_Recursive(<span class="number">class="num">15</span>));
<span class="number">class="num">15</span>.&nbsp;&nbsp;&nbsp;&nbsp; macro_Fibonacci_Interactive;
<span class="number">class="num">16</span>. }
<span class="number">class="num">17</span>. <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>

「递归与宏两种斐波那契算法的实测差异」

MT5 脚本里给出了两套斐波那契数计算路径:一个是递归函数,一个是宏展开迭代。两者结果一致,但执行特征和适用场景不同。 递归版本 Fibonacci_Recursive 默认参数取 def_Fibonacci_Element_Default = 11,调用 Fibonacci_Recursive(15) 会输出第 15 项斐波那契数 610。它代码直观,但 arg 每增大 1,函数调用次数近似指数膨胀,arg 超过 30 后可能在 MT5 策略测试器里明显卡顿。 宏 macro_Fibonacci_Interactive 用 for 循环做迭代,传入 14 时打印第 14 项 377。它零函数调用开销,适合嵌进指标实时算回撤位。外汇与贵金属杠杆高,任何数列工具只辅助定位,实际进场仍受突发新闻影响,风险偏大。 下面把核心代码逐行拆开看。

MQL5 / C++
class="macro">#define def_Fibonacci_Element_Default   class="num">11
class="macro">#define macro_Fibonacci_Interactive(arg) { \
         class="type">uint v, i, c; \
         for (c = class="num">0, i = class="num">0, v = class="num">1; c < arg; i += v, c += class="num">2) v += i;\
         Print("Result: ", (c == arg ? i : v)); \
            }
class="type">void OnStart(class="type">void)
{
    Print("Result: ", Fibonacci_Recursive(class="num">15));
    macro_Fibonacci_Interactive(class="num">14);
}
class="type">uint Fibonacci_Recursive(class="type">uint arg = def_Fibonacci_Element_Default)
{
   if (arg <= class="num">1)
      class="kw">return arg;

   class="kw">return Fibonacci_Recursive(arg - class="num">1) +  Fibonacci_Recursive(arg - class="num">2);
}

◍ 宏展开后的斐波那契交互计算

把递归改成宏内联,核心是用 for 循环在编译期展开的等价迭代里推进变量。宏 macro_Fibonacci_Interactive 接收 arg,内部声明 uint v, i, c 三个无符号整型做累加器。 循环初始化 c=0, i=0, v=1,条件 c<arg,每轮 i+=v 且 c+=2,随后 v+=i。当 arg 为偶数时循环正常结束 c==arg,取 i;否则 c 会越过 arg,用三元宏 macro_Ternário 回退取 v。 OnStart 里先以 value=15 调用 Fibonacci_Recursive 打印结果,再跑宏做交互式修正,最后 Print 出 value 验证。外汇与贵金属行情受杠杆影响大,这类代码仅作算法验证,实盘前请在 MT5 策略测试器确认数值边界,避免 uint 溢出导致回测失真。 递归版本 Fibonacci_Recursive 以 def_Fibonacci_Element_Default=11 为默认参数,arg<=1 直接返回,否则双路自调用。15 的递归结果应为 610,可作为你打开 MT5 跑这段脚本的对拍基准。

MQL5 / C++
class="macro">#define def_Fibonacci_Element_Default   class="num">11
class="macro">#define macro_Ternário(A, B, C, D) (A == B ? C : D)
class="macro">#define macro_Fibonacci_Interactive(arg) { \
    class="type">uint v, i, c; \
    for (c = class="num">0, i = class="num">0, v = class="num">1; c < arg; i += v, c += class="num">2) v += i; \
    arg = macro_Ternário(c, arg, i, v); \
}
class="type">void OnStart(class="type">void)
{
    class="type">ulong value;
    Print("Result: ", Fibonacci_Recursive((class="type">uint)(value = class="num">15)));
    macro_Fibonacci_Interactive(value);
    Print("Checking: ", value);
}
class="type">uint Fibonacci_Recursive(class="type">uint arg = def_Fibonacci_Element_Default)
{
    if (arg <= class="num">1)
        class="kw">return arg;
    class="kw">return Fibonacci_Recursive(arg - class="num">1) + Fibonacci_Recursive(arg - class="num">2);
}
把重复劳动交给小布
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到宏内联带来的内存增长预警,你专注策略逻辑就好。

常见问题

宏通过#define把代码段在编译期内联展开,函数则有调用栈开销;宏偏向简化重复片段,函数适合有独立逻辑的模块。
参数只是文本替换,没有类型检查,括号要写全避免运算优先级出错,复杂表达式建议先用函数。
倾向更快因为省了调用开销,但代码体积会指数膨胀,内存吃紧时反而可能拖慢,要按场景权衡。
可以,小布的代码体检会标记未加括号的宏参数和过度inline导致的体积异常,外汇贵金属高风险,先看清再上实盘。
因为宏本意是内联替换,塞进过程就失去省栈意义,还容易让维护者误读执行流。