MQL5 对象指针:为什么需要、如何安全使用
指针、CheckPointer、函数重载,以及链表与多态为何离不开指针
一、MQL5 里的「引用」是什么
MQL5 没有 C/C++ 那种裸指针运算(不能 &p+1 乱跳),但有「引用」概念:函数参数加 & 表示传引用而非拷贝,数组、结构体、类对象常这样传,避免大对象复制开销。理解「引用是为了高效传参、而非为了地址运算」,能避开很多从 C++ 转来的误解。
另一层是「句柄(handle)」——指标、文件、图表、时间轴对象的整数代号。MQL5 用句柄间接引用终端内部资源,你拿到的不是对象本身,而是一把钥匙。这和指针完全不同:句柄由终端管理生命周期,你不能也不该手动释放除了指标句柄之外的资源。
二、用 & 传递数组与结构体
把大数组传给函数时,写 double &arr[] 或 MqlTick &ticks[],函数内对数组的修改会反映到外面,且免去了整段复制。时间系列 price[] 就是这么传进 OnCalculate 的。结构体同理:用 & 传 struct,函数里改字段,调用方直接看到结果。
struct Signal { double ma; double rsi; bool allow; };
void Compute(Signal &sig) // 引用传递,修改外可见
{
sig.ma = iMA(_Symbol,0,20,0,MODE_SMA,0);
sig.allow = (sig.rsi > 50);
}
三、类与 new / delete
MQL5 支持面向对象,可以用 class 封装状态与行为。对象用 new 创建、delete 销毁;标准库里的 CTrade、CIndicators 都是类。和引用不同,new 出来的是堆对象,要自己负责 delete,否则内存泄漏——虽然在 EA 单次运行里影响不大,但长时间运行的脚本里会累积。
实战里常把「策略状态」封成一个类:持仓标记、上一次信号、过滤器状态都成了成员变量,OnTick 里调用成员方法更新。这比一堆全局变量清晰得多,也便于在多个 EA 间复用同一套逻辑。
四、标准库对象的生命周期
CTrade、CChart 这类标准库对象,通常作为 EA 的全局成员在 OnInit 构造一次,之后 OnTick/OnTrade 反复用。不要在每个 tick 里 new 一个 CTrade——既浪费又可能打乱其内部状态。把它们当「长期雇工」,而非「临时工」。
五、常见坑:悬空与重复释放
两个典型错误:一是 delete 了还在用的对象,之后再访问就崩;二是同一个句柄被 IndicatorRelease 两次。MQL5 没有垃圾回收,内存与句柄都得你管。规则简单:谁创建谁释放,释放后置空,访问前判空。
六、与 MQL4 的区别
MQL4 几乎没有类与引用,数组传参是隐式引用且用法含糊;MQL5 明确区分值传递与引用传递,且引入完整 OOP。从 MQL4 迁过来的人,最大的坑是「以为传了副本其实改了原值」或反之。看清函数签名里有没有 &,就不会懵。
七、实战:用类管理策略状态
把下面的状态收进一个类,比散落全局变量稳得多:当前是否有持仓、上一次信号方向、过滤器是否放行、连续亏损计数。OnTick 里调用类的 Update(),类内部维护一切,EA 主逻辑只读结论。这样状态迁移有单点,调试时一眼看清。
class CStrategyState {
bool hasPos;
int lastSignal;
int consecLoss;
void OnFill(bool win) { if(!win) consecLoss++; else consecLoss=0; }
};
CStrategyState st; // 全局,OnInit 后全程复用
八、用句柄驱动指标:CopyBuffer 实战
引用概念最终要落到「用句柄拿数据」这件事上。指标在 OnInit 里用 iMA、iRSI、iCustom 拿到一个整数 handle 后,真正的数据并不在 handle 里,而在终端管理的指标缓冲区。要读它,必须用 CopyBuffer(handle, buffer_index, start_pos, count, array) 把一段值拷进你声明的数组,handle 只是开门的钥匙,而不是数据本身。
一个常见疏漏:CopyBuffer 的返回值是「实际拷贝到的元素个数」,不是成功与否的布尔。若 handle 还没准备好(指标仍在计算首根 K 线)、或请求的柱数超过可用范围,返回值会小于你期望的 count。稳健写法是在调用后检查 rates_total 是否足够,并给目标数组预先分配长度,避免用到未初始化的 0 值去发信号。
double maBuf[];
int maH = iMA(_Symbol, PERIOD_CURRENT, 14, 0, MODE_EMA, PRICE_CLOSE);
int copied = CopyBuffer(maH, 0, 0, 3, maBuf);
if(copied < 3) { /* 数据未就绪,跳过本 tick */ return; }
double maNow = maBuf[0], maPrev = maBuf[1];
九、引用语义与复制陷阱
当函数参数是 &arr[] 或 &struct 时,函数内改的是同一份内存;而普通值传参(如传递一个 double、int)则是拷贝,函数里改了不影响外面。这个区别决定 bug 形态:用引用传大结构体省内存且可回写,但一旦函数里把它改乱,调用方也会跟着乱,调试时要在「谁动了这块内存」上花心思。
另一个隐蔽点:MQL5 里字符串、动态数组的赋值默认是拷贝语义,即 a = b 之后改 a 不影响 b;但类对象若存的是引用或指针,多个地方指向同一实例就会互相影响。写 EA 最稳的纪律是:跨事件(OnTick、OnTrade、定时器)共享的状态,统一收进类成员,明确它是单例,别到处 new 副本,否则状态会出现「读到的不是最新那个」的诡异现象。