从基础到中级:按值传递还是按引用传递·进阶篇
📘

从基础到中级:按值传递还是按引用传递·进阶篇

第 2/2 篇

引用传递的隐性副作用

MQL5 里用 & 符号声明参数就是引用传递,编译器把它当成指向原变量的指针,两者共用同一块内存。代码 04 第 13 行写了 void Procedure(int &arg1),第 16 行对 arg1 加值,实际直接改掉了 OnStart 里的 info,终端输出图 03 第四行变成 11 而非图 01 的 10。 这种写法最坑的地方在于:你以为只在例程里动了 arg1,其实 info 也被改了。复杂项目里几百行跨文件调用,定位这类问题可能耗掉数小时甚至数天。除非必须让例程回写多个值,否则基本类型宁可用函数返回值也别用引用改原值。 如果确实要引用传递,也要写成显式可控的形式,像代码 07 第 9 行直接 info = Function(info) 把修改点暴露在调用处,跑出来图 04 的结果,一眼能看懂谁改了谁。 另一个高频错误是参数位置填反。代码 09 里 Function(info, dt) 本意是 info 收秒数、dt 收日期,但第 10 行写成了 Function(dt, info);datetime 和 ulong 都是 8 字节,编译器只给图 06 的警告不报错,运行后图 07 输出完全错乱。真在 MT5 里复现这段,把第 10 行两参数对调并修正类型转换才能拿到图 09 的正确值。外汇贵金属 EA 开发本就高风险,这类引用错序会在实盘放大亏损概率,改完务必在策略测试器单步验证。

MQL5 / C++
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">int info = class="num">10;
class="num">07.
class="num">08.     Print(__FUNCTION__, " : #class="num">1 => ", info);
class="num">09.     Procedure(info);
class="num">10.     Print(__FUNCTION__, " : #class="num">2 => ", info);
class="num">11. }

「引用传参改写引发的编译报错」

在 MQL5 里把函数形参声明为 int &arg1(非常量引用)时,编译器允许函数体内直接修改 arg1 的值,并且这个改动会回写到调用方的实参。下面这段脚本把 info 初始化为 10,调用 Procedure(info) 后,OnStart 里打印的 #2 会变成 13,因为 arg1 = arg1 + 3 改的是同一块内存。 [CODE] //+------------------------------------------------------------------+ void Procedure(int & arg1) { Print(__FUNCTION__, " : #1 => ", arg1); arg1 = arg1 + 3; Print(__FUNCTION__, " : #2 => ", arg1); } //+------------------------------------------------------------------+ #property copyright "Daniel Jose" void OnStart(void) { int info = 10; Print(__FUNCTION__, " : #1 => ", info); Procedure(info); Print(__FUNCTION__, " : #2 => ", info); } //+------------------------------------------------------------------+ [/CODE] 逐行拆解:第 13 行 void Procedure(int & arg1)& 表示按引用传递,实参 info 的地址被传入;第 16 行 arg1 = arg1 + 3 在原函数内把引用目标加 3;第 9 行 Procedure(info) 调用后,第 10 行打印 info 得到 13 而非 10。 一旦把形参改成 const int & arg1,函数体内再写 arg1 = arg1 + 3 会直接编译失败——const 引用禁止改写。此时只能打印 arg1 + 3 而不回写,OnStart 中的 info 始终保持 10。开 MT5 新建脚本粘入两段代码分别编译,能在编译日志里看到第二段报 'assignment to const reference' 类错误,这是验证引用与常量引用差异的最快办法。外汇与贵金属交易脚本涉及真金白银,参数传递写错可能让 EA 在实盘误改风控变量,务必在策略测试器先跑通再上模拟盘。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Procedure(class="type">int & arg1)
{
   Print(__FUNCTION__, " : #class="num">1 => ", arg1);
   arg1 = arg1 + class="num">3;
   Print(__FUNCTION__, " : #class="num">2 => ", arg1);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class="type">void OnStart(class="type">void)
{
   class="type">int info = class="num">10;
   Print(__FUNCTION__, " : #class="num">1 => ", info);
   Procedure(info);
   Print(__FUNCTION__, " : #class="num">2 => ", info);
}
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 引用传递改值后返回值的连锁反应

MQL5 里函数参数带 & 符号就是引用传递,函数体内改了它,调用方原变量跟着变。下面三段脚本把这件事拆开给你看,直接丢进 MT5 跑就能对照输出。 第一段里 Function 用 const int & 接收,编译会报错——const 引用不能在函数内赋值,所以原文第 13 行那种写法根本过不了编译器,真实可跑版是把 const 去掉。 第二段去掉 const,Function 里执行 return (arg1 = arg1 + 3) + 3。OnStart 初始 info=10,进函数前打印 #1=>10;函数内 arg1 变成 13,返回值 16;回到 OnStart 打印 #2=>16,同时外部 info 已被改成 13。一个调用同时改了外部变量和拿到了加过两次 3 的结果。 第三段混了 uint 和 datetime:info=10,dt=2024.07.17。Function 内 arg1 被乘 3600(秒),info 变成 36000,返回 dt+36000 秒即加 10 小时,dt 变成 2024.07.17 10:00。注意返回行做了 (datetime) 强转,否则 ulong*3600 的类型推演可能告警。外汇与贵金属脚本跑这类时间运算时杠杆高、时点错一位止损就偏,务必先在策略测试器用历史数据验证。 引用传参最容易被忽略的是:你以为只是算个返回值,其实外部变量已经被悄悄改写,多个函数嵌套调用时这种隐性状态变化会让调试难度陡增。

MQL5 / C++
class="type">void OnStart(class="type">void)
{
   class="type">int info = class="num">10;
   Print(__FUNCTION__, " : #class="num">1 => ", info);
   info = Function(info);
   Print(__FUNCTION__, " : #class="num">2 => ", info);
}
class="type">int Function(class="type">int &arg1)
{
   Print(__FUNCTION__, " : #class="num">1 => ", arg1);
   class="kw">return (arg1 = arg1 + class="num">3) + class="num">3;
}

class="type">void OnStart(class="type">void)
{
   class="type">uint     info = class="num">10;
   class="type">class="kw">datetime dt = D&class="macro">#x27;class="num">17.07.class="num">2024&class="macro">#x27;;
   Print(__FUNCTION__, " : #class="num">1 => ", info, " :: ", dt);
   dt = Function(dt, info);
   Print(__FUNCTION__, " : #class="num">2 => ", info, " :: ", dt);
}
class="type">class="kw">datetime Function(class="type">ulong &arg1, class="type">class="kw">datetime arg2)
{
   Print(__FUNCTION__, " : #class="num">1 => ", arg1, " :: ", arg2);
   class="kw">return arg2 + (class="type">class="kw">datetime)(arg1 = arg1 * class="num">3600);
}

带引用参数的时间函数签名

MQL5 里函数参数若加 & 符号,表示按引用传递,调用方变量可能被改写;不加 & 的 datetime 参数则是值拷贝,函数内改了不影响外部。 下面这行声明里,arg1 用 uint & 接收,适合在函数内直接累加计数器或序号;arg2 是 datetime 值传入,常用于传入某个 K 线时间或订单时间做比对。 开 MT5 按 F4 新建脚本,把这段声明原样贴进全局作用域,编译器不会报错即说明引用与值参数混用合法,可在此基础补函数体做时间差计算。

MQL5 / C++
class="type">class="kw">datetime Function(class="type">uint &arg1, class="type">class="kw">datetime arg2)

「画得少,看得清」

把指针、内存布局这些底层机制啃透之后,写 MQL5 反而比早年折腾 C/C++ 轻松。原文作者自述花了很长时间才弄明白代码为何出现难以解释的怪异行为,一旦底层清晰,上层应用就直观多了。 下一篇才会正式进入指标与实用案例,这一篇的价值在于替你省掉「明明代码能跑但就是不对劲」的排查成本。 外汇与贵金属自带高杠杆风险,即便逻辑再顺,也先在 MT5 策略测试器里用小资金验证再上实盘。

常见问题

这是引用传递的隐性副作用:形参绑定实参地址,函数内改写会直接作用到原变量。若不想被改,改用按值传递或加 const 限定。
引用参数必须接收可修改的左值,不能传字面量或临时表达式。检查调用处是否写了具体变量,而非直接计算式。
可以。把代码贴给小布,它能标出带 & 的参数并提示哪些函数会改写实参,省去你逐行找副作用。
引用改值可能先于返回语句执行,引发连锁覆盖。建议分开接收返回值和引用输出,避免同一变量既当容器又当信号。
只留近 20 根 K 线内被触碰 2 次以上的位。画得少反而能看清真实反应区,减少噪声干扰。