在 MQL5 中使用对象指针·综合运用
(3/3)·从无效指针崩溃到函数传参与多态,把前两周的概念一次性用顺
「链表头插法与析构时的级联释放」
在 MT5 自建 CList 容器时,把新节点塞到表头只需三步:先把原头节点退位为「后节点」,记下它的指针,再把 m_first 指向新 item,最后用 m_first.next(p) 接回老头节点。这样插入是 O(1),不会遍历整条链。 析构要小心内存。CList 析构只 delete 了 first,真正的级联清理发生在 CListItem 析构里:若 m_next 是动态指针就递归 delete,并打印「移除项目 ID =」;否则只提示后节点未定义。跑 7 个节点的例子,列表析构后会从尾到头逐个回收。 下面这段可直接贴进 MT5 脚本验证:循环 new 出 0~6 号 CListItem 并 addItem,Print 出「列表中有 7 个项目」,切回图表结束脚本时观察日志里 ID 被删的顺序。外汇与贵金属 EA 用这类结构管订单对象时,动态释放不全会漏内存,属高风险操作。
m_first.prev(item); class=class="str">"cmt">//--- 保存当前第一个项目的指针 CListItem *p=m_first; class=class="str">"cmt">//--- 把新增项目作为第一个项目 m_first=item; class=class="str">"cmt">//--- 把原来第一个项目作为新增项目的后一个项目 m_first.next(p); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 列表析构函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ CList::~CList(class="type">void) { class="type">int ID=first.getID(); if(CheckPointer(first)==POINTER_DYNAMIC) class="kw">delete(first); Print(__FUNCTION__," 第一个项目 ID =",ID," 被删除"); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 项目析构函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ CListItem::~CListItem(class="type">void) { if(CheckPointer(m_next)==POINTER_DYNAMIC) { class="kw">delete(m_next); Print(__FUNCTION__," 移除项目 ID =",m_ID); } else Print(__FUNCTION__," 此项目的后一个项目没有定义ID=",m_ID); } class="type">void OnStart() { class=class="str">"cmt">//--- CList list; for(class="type">int i=class="num">0;i<class="num">7;i++) { CListItem *item=new CListItem; item.setID(i); list.addItem(item); } Print("列表中有 ",list.size()," 个项目"); }
◍ 虚函数才是多态的开关
在 MT5 里写 EA 或指标时,常遇到一类需求:直线、三角形、矩形、圆形态各异,却都要能「绘制」。建一个基类 CShape,让各类图形作为它的后代去继承,是顺手的做法。 父类里放两个打算在子类重写的函数:Draw() 负责画,getTypeName() 回传形状名。声明一个 CShape 指针数组 *shapes[],把不同派生类的实例地址塞进去,循环里统一调 getTypeName()。 第一次跑容易懵——日志里 5 行全打印「Shape」:2010.03.18 14:06:18 在 EURUSD H1 上,索引 0~4 无一例外。原因是数组按 CShape 指针声明,编译器只认基类的实现,派生类自己的版本被静默忽略了。 把基类 getTypeName() 前加 virtual 关键字再跑,输出立刻变样:同是 15:01:11 那次,索引 0 是 Shape、1 是 Line、2 是 Triangle、3 是 Rectangle、4 是 Circle。运行时根据实际对象类型 dispatch,这才是真多态。 虚函数一旦落地,就能给每个派生类写完整功能的 Draw(),随机形状上图交给随附脚本验证即可。外汇与贵金属杠杆高,这类封装实验先在 demo 账户跑,实盘误用对象指针有崩脚本风险。
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| 形状对象的基类 |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class</span> CShape { <span class="keyword">class="kw">private</span>: <span class="keyword">class="type">int</span> m_type; <span class="keyword">class="kw">public</span>: CShape(){m_type=<span class="number">class="num">0</span>;} <span class="keyword">class="type">void</span> Draw(); <span class="built_in">class="type">class="kw">string</span> getTypeName(){<span class="keyword">class="kw">return</span>(<span class="class="type">class="kw">string">"Shape"</span>);} }; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| 线性对象的类 |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class</span> CLine:<span class="keyword">class="kw">public</span> CShape { <span class="keyword">class="kw">private</span>: <span class="keyword">class="type">int</span> m_type; <span class="keyword">class="kw">public</span>: CLine(){m_type=<span class="number">class="num">1</span>;} <span class="keyword">class="type">void</span> Draw(); <span class="built_in">class="type">class="kw">string</span> getTypeName(){<span class="keyword">class="kw">return</span>(<span class="class="type">class="kw">string">"Line"</span>);} }; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| 三角形对象的类 |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class</span> CTriangle:<span class="keyword">class="kw">public</span> CShape { <span class="keyword">class="kw">private</span>: <span class="keyword">class="type">int</span> m_type; <span class="keyword">class="kw">public</span>: CTriangle(){m_type=<span class="number">class="num">2</span>;} <span class="keyword">class="type">void</span> Draw(); <span class="built_in">class="type">class="kw">string</span> getTypeName(){<span class="keyword">class="kw">return</span>(<span class="class="type">class="kw">string">"Triangle"</span>);} }; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| 长方形对象的类 |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class</span> CRectangle:<span class="keyword">class="kw">public</span> CShape { <span class="keyword">class="kw">private</span>: <span class="keyword">class="type">int</span> m_type; <span class="keyword">class="kw">public</span>:
用基类指针统一调度图形对象
在 MT5 的自定义指标或脚本里,若要把线段、三角形、矩形、圆都当「图形」批量处理,可以建一个基类 CShape,再让各具体图形类继承它。基类里放一个 m_type 整型字段标识类型,派生类构造时赋不同值:矩形填 3,圆形填 4。 下面这段可直接贴进 MT5 脚本跑,看多态怎么用指针数组统一管五种图形: class CShape { private: int m_type; public: CShape(){m_type=0;} void Draw(); virtual string getTypeName(){return("Shape");} }; class CLine:public CShape { private: int m_type; public: CLine(){m_type=1;} void Draw(); string getTypeName(){return("Line");} }; class CTriangle:public CShape { private: int m_type; public: CTriangle(){m_type=2;} void Draw(); string getTypeName(){return("Triangle");} }; class CRectangle:public CShape { private: int m_type; public: CRectangle(){m_type=3;} void Draw(); string getTypeName(){return("Rectangle");} }; class CCircle:public CShape { private: int m_type; public: CCircle(){m_type=4;} void Draw(); string getTypeName(){return("Circle");} }; void OnStart() { CShape *shapes[]; ArrayResize(shapes,5); shapes[0]=new CShape; shapes[1]=new CLine; shapes[2]=new CTriangle; shapes[3]=new CRectangle; shapes[4]=new CCircle; for(int i=0;i<5;i++) { Print(i,shapes[i].getTypeName()); } for(int i=0;i<5;i++) delete(shapes[i]); } 逐行拆解:CShape *shapes[] 声明基类指针数组;ArrayResize(shapes,5) 把数组扩到 5 个元素;随后每个下标 new 出不同派生类对象,但都塞进基类指针里。循环里 shapes[i].getTypeName() 实际调的是各派生类自己的实现,依次打印出 Shape、Line、Triangle、Rectangle、Circle。最后第二个循环 delete 掉全部对象,避免 MT5 脚本退出后内存泄漏。 实盘外接这类图形对象时,外汇与贵金属波动剧烈、杠杆高风险大,图形只是辅助标记;跑完上面脚本你会在 MT5 专家日志看到 0 Shape 到 4 Circle 共 5 行输出,验证多态调度是否按预期工作。
class CShape { class="kw">private: class="type">int m_type; class="kw">public: CShape(){m_type=class="num">0;} class="type">void Draw(); class="kw">virtual class="type">class="kw">string getTypeName(){class="kw">return("Shape");} }; class CLine:class="kw">public CShape { class="kw">private: class="type">int m_type; class="kw">public: CLine(){m_type=class="num">1;} class="type">void Draw(); class="type">class="kw">string getTypeName(){class="kw">return("Line");} }; class CTriangle:class="kw">public CShape { class="kw">private: class="type">int m_type; class="kw">public: CTriangle(){m_type=class="num">2;} class="type">void Draw(); class="type">class="kw">string getTypeName(){class="kw">return("Triangle");} }; class CRectangle:class="kw">public CShape { class="kw">private: class="type">int m_type; class="kw">public: CRectangle(){m_type=class="num">3;} class="type">void Draw(); class="type">class="kw">string getTypeName(){class="kw">return("Rectangle");} }; class CCircle:class="kw">public CShape { class="kw">private: class="type">int m_type; class="kw">public: CCircle(){m_type=class="num">4;} class="type">void Draw(); class="type">class="kw">string getTypeName(){class="kw">return("Circle");} }; class="type">void OnStart() { CShape *shapes[]; ArrayResize(shapes,class="num">5); shapes[class="num">0]=new CShape; shapes[class="num">1]=new CLine; shapes[class="num">2]=new CTriangle; shapes[class="num">3]=new CRectangle; shapes[class="num">4]=new CCircle; for(class="type">int i=class="num">0;i<class="num">5;i++) { Print(i,shapes[i].getTypeName()); } for(class="type">int i=class="num">0;i<class="num">5;i++) class="kw">delete(shapes[i]); }
「画得少,看得清」
MQL5 里对象的创建和销毁都由运行时自动接管,日常写策略没必要主动碰指针。真遇到多态、动态对象管理这类场景非用不可,动手前先跑一遍 CheckPointer() 确认有效性,这个函数就是为这种边界情况留的。 还有一点容易踩坑:MQL5 的指针不是 C++ 那种直接映射内存地址的实体指针,把它塞进 DLL 的输入参数大概率出事,跨模块调用时别这么干。 附带示例里 drawmanyobjects.mq5 有 10.4 KB,demopolymorphism.mq5 仅 3.21 KB,前者刻意堆了多种图形对象、后者用多态精简结构——同样的需求,少画冗余对象、靠指针理清关系,代码体积和可读性差距一眼能看出来。开 MT5 把这两个文件都跑一遍,比看文档直观。