面向对象编程基础·综合运用
📘

面向对象编程基础·综合运用

第 3/3 篇

「用类封装挂单批量删除逻辑」

在 MT5 脚本里想一次性清掉某类挂单,最忌讳把判断逻辑写死在主函数里。把参数和动作收进一个 COrderDelete 类,后面换品种或换订单类型只用改 Init 传入的布尔值,不用动核心循环。 这个类头一行就 #include <Trade/Trade.mqh>,靠 CTrade 对象发删除指令;私有成员用 7 个 bool 分别管跨品种开关与六种挂单类型(BuyStop / SellStop / BuyLimit / SellLimit / BuyStopLimit / SellStopLimit)。 Delete() 里先拿 OrdersTotal() 做上限跑 for 循环,用 OrderGetTicket(i) 取第 i 张单号,若返回大于 0 才算选中成功。选中后读 ORDER_TYPE,按类型逐个比对开关,没开权限的就 continue 跳过——比如 m_BuyStop 为 false 时,所有 ORDER_TYPE_BUY_STOP 单子原样留着。 外汇和贵金属挂单受点差与报价断流影响,脚本跑之前先确认账户允许对应类型操作,批量删单失败概率会随挂单数量上升而走高。

MQL5 / C++
class="macro">#include <Trade/Trade.mqh>
class COrderDelete
  {
class="kw">private:
  class=class="str">"cmt">// 参数变量
  class="type">bool               m_AllSymbol;
  class="type">bool               m_BuyStop;
  class="type">bool               m_SellStop;
  class="type">bool               m_BuyLimit;
  class="type">bool               m_SellLimit;
  class="type">bool               m_BuyStopLimit;
  class="type">bool               m_SellStopLimit;
  class=class="str">"cmt">// 加载 CTrade 类
  CTrade             m_Trade;
class="kw">public:
  class=class="str">"cmt">// 设置参数函数
  class="type">void Init(class="type">bool aAllSymbol,class="type">bool aBuyStop,class="type">bool aSellStop,class="type">bool aBuyLimit,class="type">bool aSellLimit,class="type">bool aBuyStopLimit,class="type">bool aSellStopLimit)
    {
    class=class="str">"cmt">// 设置参数
    m_AllSymbol     =aAllSymbol;
    m_BuyStop       =aBuyStop;
    m_SellStop      =aSellStop;
    m_BuyLimit      =aBuyLimit;
    m_SellLimit     =aSellLimit;
    m_BuyStopLimit =aBuyStopLimit;
    m_SellStopLimit=aSellStopLimit;
    }
  class=class="str">"cmt">// 删除订单主函数
  class="type">bool Delete()
    {
    class=class="str">"cmt">// 检查函数结果变量
    class="type">bool m_Ret=true;
    class=class="str">"cmt">// 所有订单在客户端循环
    for(class="type">int i=class="num">0; i<OrdersTotal(); i++)
      {
      class=class="str">"cmt">// 选择订单并取单号
      class="type">class="kw">ulong m_Ticket=OrderGetTicket(i);
      class=class="str">"cmt">// 选择成功
      if(m_Ticket>class="num">0)
        {
        class="type">long m_Type=OrderGetInteger(ORDER_TYPE);
        class=class="str">"cmt">// 检查订单类型
        if(m_Type == ORDER_TYPE_BUY_STOP && !m_BuyStop) class="kw">continue;
        if(m_Type == ORDER_TYPE_SELL_STOP && !m_SellStop) class="kw">continue;
        if(m_Type == ORDER_TYPE_BUY_LIMIT && !m_BuyLimit) class="kw">continue;
        if(m_Type == ORDER_TYPE_SELL_LIMIT && !m_SellLimit) class="kw">continue;
        if(m_Type == ORDER_TYPE_BUY_STOP_LIMIT && !m_BuyStopLimit) class="kw">continue;
        if(m_Type == ORDER_TYPE_SELL_STOP_LIMIT && !m_SellStopLimit) class="kw">continue;

◍ 挂单批量清理的类的调用入口

这段脚本把挂单删除逻辑封装成了 COrderDelete 类,外部只暴露几个 input 开关和 OnStart 入口,实盘里改参数比改核心循环安全得多。 AllSymbol 默认 false,意味着脚本只删当前图表品种的挂单;若设 true,则遍历账户内所有品种。六种挂单类型(BuyStop / SellStop / BuyLimit / SellLimit / BuyStopLimit / SellStopLimit)各自独立 bool 开关,默认全 false,不显式开启哪一种就不会删那一种。 OnStart 里先 od.Init(...) 把七个外部参数灌进类,再调 od.Delete() 拿返回 bool。删除成功弹「脚本结束成功」,否则弹「脚本结束错误, 参见日志详情」——日志里具体哪单没删掉,得回看类内 OrderDelete 失败时的 Print 输出。 外汇与贵金属挂单清理涉及真实仓位风险,脚本误删 Limit 单可能造成预期入场落空,跑之前务必在策略测试器用 demo 账户验一遍开关组合。

MQL5 / C++
class=class="str">"cmt">// Check symbol
if(!m_AllSymbol && Symbol()!=OrderGetString(ORDER_SYMBOL)) class="kw">continue;
class=class="str">"cmt">// 删除
if(!m_Trade.OrderDelete(m_Ticket))
  {
   m_Ret=class="kw">false; class=class="str">"cmt">// 删除失败
  }
     }
   class=class="str">"cmt">// 选择订单失败, 未知结果,
   class=class="str">"cmt">// 函数出错结束
   else
     {
      m_Ret=class="kw">false;
      Print("选择订单错误");
     }
   }
class=class="str">"cmt">// 返回函数结果
class="kw">return(m_Ret);
 }
};
class=class="str">"cmt">// 外部参数
class=class="str">"cmt">// 选择交易品种。 true  - 删除所有交易品种的订单,
class=class="str">"cmt">//                  class="kw">false - 仅删除脚本运行所在图表对应交易品种的订单
input class="type">bool AllSymbol=class="kw">false;
class=class="str">"cmt">// 选择删除的订单类型
input class="type">bool BuyStop      = class="kw">false;
input class="type">bool SellStop     = class="kw">false;
input class="type">bool BuyLimit     = class="kw">false;
input class="type">bool SellLimit    = class="kw">false;
input class="type">bool BuyStopLimit = class="kw">false;
input class="type">bool SellStopLimit = class="kw">false;
class=class="str">"cmt">// 包含类文件
class="macro">#include <OOP_CDeleteOrder_1.mqh>
class=class="str">"cmt">// 加载类
COrderDelete od;
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void OnStart()
  {
class=class="str">"cmt">// 传递外部参数至类
   od.Init(AllSymbol,BuyStop,SellStop,BuyLimit,SellLimit,BuyStopLimit,SellStopLimit);
class=class="str">"cmt">//--- 删除文件
   class="type">bool Ret=od.Delete();
class=class="str">"cmt">// 处理删除结果
   if(Ret)
      {
       Alert("脚本结束成功");
      }
   else
      {
       Alert("脚本结束错误, 参见日志详情");
      }
  }

类的自动启停:构造与析构

MT5 里 EA 和指标靠 OnInit()、OnDeinit() 管启动和收尾,类也有自己的对应机制——构造函数和析构函数,分别在类实例创建和销毁时自动跑,你从代码里永远看不到显式调用。 构造函数的写法是起一个跟类名完全一样的函数;析构函数则在前面加波浪符 ~。拿下面这段验证:脚本只做一件事,让实例暂停 3 秒,但跑起来会先弹「构造函数」警报,3 秒后弹「析构器」警报。 这种自动性在写指标辅助类时很实用:比如构造阶段把外部参数读好、在图表画好辅助线,析构阶段统一删对象,避免 MT5 退出时残留图形。外汇和贵金属行情跳空频繁,这类清理逻辑能降低误绘概率,但市场高风险仍在,自动化不替你兜底。

MQL5 / C++
class=class="str">"cmt">// Class
class CName 
  {
class="kw">public:
  class=class="str">"cmt">// 类
                      CName() { Alert("构造函数"); }
  class=class="str">"cmt">// 析构函数
                      ~CName() { Alert("析构器"); }
  class="type">void Sleep() { Sleep(class="num">3000); }
  };
class=class="str">"cmt">// 加载类
CName cname;
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void OnStart() 
  {
class=class="str">"cmt">// 暂停
  cname.Sleep();
  }

「把参数塞进构造函数省掉手动 Init」

把脚本改成类的时候,初始化那一行其实可以省掉。做法是在类里加一个构造函数,让它直接在对象创建时把参数接进去,然后内部调一次 Init()。 构造函数的所有参数都带默认值,所以老写法不受影响:不传参创建对象,之后再手动调 Init() 也行。 但有了构造函数后,更顺手的用法是创建对象时就把筛选条件传进去,连 Init() 都不用显式调用。下面这段就是带参构造的定义和调用样例。 Init() 仍留在 public 区,好处是能随时重新初始化。比如跑 EA 时,这一波只想撤 Stop 单,下一波只想撤 Limit 单,用不同参数再调一次 Init() 就能让 Delete() 删不同的挂单组。 随文给出的 OOP_CDeleteOrder_2.mqh 和 OOP_sDeleteOrders_3.mq5 里能直接看到这套写法,开 MT5 加载后可改参数验证删单范围。

MQL5 / C++
COrderDelete(<span class="keyword">class="type">bool</span> aAllSymbol&nbsp;&nbsp;&nbsp;&nbsp; = <span class="keyword">class="kw">false</span>,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">bool</span> aBuyStop&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <span class="keyword">class="kw">false</span>,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">bool</span> aSellStop&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= <span class="keyword">class="kw">false</span>,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">bool</span> aBuyLimit&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= <span class="keyword">class="kw">false</span>,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">bool</span> aSellLimit&nbsp;&nbsp;&nbsp;&nbsp; = <span class="keyword">class="kw">false</span>,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">bool</span> aBuyStopLimit&nbsp;&nbsp;= <span class="keyword">class="kw">false</span>,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">bool</span> aSellStopLimit=<span class="keyword">class="kw">false</span>) 
&nbsp;&nbsp;{
&nbsp;&nbsp; Init(aAllSymbol,aBuyStop,aSellStop,aBuyLimit,aSellLimit,aBuyStopLimit,aSellStopLimit);
&nbsp;&nbsp;}
COrderDelete od(AllSymbol,BuyStop,SellStop,BuyLimit,SellLimit,BuyStopLimit,SellStopLimit);

◍ 同一类挂不同参数跑两类删单

在 MT5 的 EA 设计里,一个类没必要每次换逻辑就重新初始化。只要清楚这个类负责什么,就可以在初始化阶段载入多个带不同参数的实例,各自管各自的事。 以删除挂单为例:实盘里经常碰到两种需求,一种只清 BuyStop 和 BuyLimit,另一种只清 SellStop 和 SellLimit。与其写两套类或在运行中去改成员,不如直接 new 两个实例,把布尔开关按需要铺开。 下面这段就是典型用法——DeleteBuy 管买入类挂单,DeleteSell 管卖出类挂单,参数顺序一一对应六类订单加一个总开关。外汇与贵金属杠杆高,挂单批量删除前务必确认魔术码与品种过滤,误删可能直接吃滑点。 代码逐行看:第一行 COrderDelete 构造时传入七個布尔,DeleteBuy 的第2、4位为 true,即 BuyStop、BuyLimit 开启删除;第二行 DeleteSell 的第3、5位为 true,对应 SellStop、SellLimit;后两行各自调 Delete() 执行。

MQL5 / C++
COrderDelete DeleteBuy(class="kw">false,true,class="kw">false,true,class="kw">false,class="kw">false,class="kw">false);
COrderDelete DeleteSell(class="kw">false,class="kw">false,true,class="kw">false,true,class="kw">false,class="kw">false);
DeleteBuy.Delete();
DeleteSell.Delete();

动态指针数组里的构造与泄漏

在 MT5 脚本里,若运行前不确定要实例化多少个类对象,就得用对象数组。和普通变量数组不同,声明时带星号 "*" 意味着用的是动态指针,而非自动变量。 可以预先定死大小,比如 CName* cname[10];也能用动态数组 CName* cname[],随后在 OnStart() 里用 ArrayResize(cname,10) 扩容。两者本质都是指针槽,真正装对象要靠循环里 new CName(i) 逐个注入。 跑一段不写 delete 的脚本,日志会刷出 10 次「构造函数」弹窗,却看不到析构函数,终端 Experts 标签页还会报内存泄漏——动态指针持有的对象不会在程序退出时自动释放。 补上循环 delete(cname[i]) 后,10 个析构函数依次触发,报错消失。外汇与贵金属 EA 开发中这类泄漏在高频重建对象时概率更高,建议封装成 RAII 风格或统一清理函数。

MQL5 / C++
class=class="str">"cmt">// Class
class CName 
  {
class="kw">private:
   class="type">int               m_arg; class=class="str">"cmt">// 类
class="kw">public:
  class=class="str">"cmt">// 构造函数
   CName(class="type">int aArg) 
    {
      m_arg=aArg;
      Alert("构造函数"+IntegerToString(m_arg));
    }
  class=class="str">"cmt">// 析构函数
  ~CName() 
     { 
      Alert("析构函数 "+IntegerToString(m_arg)); 
     }
  class=class="str">"cmt">//---
  class="type">void Sleep() 
     { 
      Sleep(class="num">3000); 
     }
  };
CName* cname[class="num">10];
CName* cname[];
ArrayResize(cname,class="num">10);
ArrayResize(cname,class="num">10);
for(class="type">int i=class="num">0; i<class="num">10; i++) 
  {
   cname[i]=new CName(i);
  }
cname[class="num">0].Sleep();
for(class="type">int i=class="num">0; i<class="num">10; i++) 
  {
   class="kw">delete(cname[i]);
  }

「用虚函数把比较逻辑交给子类」

多态是面向对象里最值得盯紧的一块:它让同一段调用代码,在不同子类上跑出不同行为。典型做法是一个基类挂虚函数,再派生多个子类各自实现,比较两值就能拆出大于、小于、大于等于、小于等于、等于五种形态。 基类里的虚函数本身不写实质逻辑,只做接口占位。声明时以 virtual 开头,例如 virtual bool CheckVariant(int Var1,int Var2) 默认返回 false,相当于一个可插拔的连接器。 下面这段是五种比较的完整类结构。基类 CCheckVariant 之后,CVariant1 到 CVariant5 分别覆盖成 >、<、>=、<=、== 的实现。 如果提前知道要用哪种比较,直接用对应子类类型声明指针;若运行时才定,就用基类指针配合 new 动态载入子类。无论哪种,调用 CheckVariant 的代码完全一致,结果却由子类决定。该示例在随附的 OOP_sVariant_1.mq5 中可直跑验证。 别把虚函数当语法糖 它在 EA 里真正的价值是少写 if-else 分支。比如切换策略的触发条件时,换子类比改判断块安全,也方便小布类工具做变体回测。外汇与贵金属波动剧烈,任何逻辑改动都先上 MT5 用历史数据跑一遍再上实盘。

MQL5 / C++
<span class="keyword">class</span> CCheckVariant
&nbsp;&nbsp;{
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">bool</span> CheckVariant(<span class="keyword">class="type">int</span> Var1,<span class="keyword">class="type">int</span> Var2)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>(<span class="keyword">class="kw">false</span>);
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;};
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp; &gt;&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span>
<span class="keyword">class</span> CVariant1: <span class="keyword">class="kw">public</span> CCheckVariant
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">class="type">bool</span> CheckVariant(<span class="keyword">class="type">int</span> Var1,<span class="keyword">class="type">int</span> Var2)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>(Var1&gt;Var2);
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;};
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp; &lt;&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span>
<span class="keyword">class</span> CVariant2: <span class="keyword">class="kw">public</span> CCheckVariant
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">class="type">bool</span> CheckVariant(<span class="keyword">class="type">int</span> Var1,<span class="keyword">class="type">int</span> Var2)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>(Var1&lt;Var2);
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;};
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp; &gt;=&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span>
<span class="keyword">class</span> CVariant3: <span class="keyword">class="kw">public</span> CCheckVariant
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">class="type">bool</span> CheckVariant(<span class="keyword">class="type">int</span> Var1,<span class="keyword">class="type">int</span> Var2)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>(Var1&gt;=Var2);
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;};
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp; &lt;=&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span>
<span class="keyword">class</span> CVariant4: <span class="keyword">class="kw">public</span> CCheckVariant
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">class="type">bool</span> CheckVariant(<span class="keyword">class="type">int</span> Var1,<span class="keyword">class="type">int</span> Var2)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>(Var1&lt;=Var2);
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;};
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span>
<span class="comment">class=class="str">"cmt">//|&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------</span>
<span class="keyword">class</span> CVariant5: <span class="keyword">class="kw">public</span> CCheckVariant
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">class="type">bool</span> CheckVariant(<span class="keyword">class="type">int</span> Var1,<span class="keyword">class="type">int</span> Var2)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>(Var1==Var2);
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;};

◍ 用 switch 分流五类条件检查实例

在 MT5 的 EA 或指标工程里,若想按运行模式切换不同的条件判定逻辑,可先声明一个基类指针,再用整型变量控制具体子类的实例化。下面这段写法把 Variant 固定为 5,意味着编译后只有五分之一的分支会被实际走到,其余四个 new 在当次运行中不会触发。 CVariant1 var; 这一行只是头注释里说的加载类声明,真正干活的是 CCcheckVariant* var 这个多态指针;switch(Variant) 里 case 1 到 5 分别 new 出 CVariant1~CVariant5,每个分支后必须 break,否则会连续构造多个对象导致内存泄漏。 最后一行 bool rv = var.CheckVariant(1,2); 用实例化出的对象去比两个入参,返回布尔值供后续信号过滤。外汇与贵金属市场波动剧烈、杠杆风险高,这类条件分支仅用于策略逻辑分流,不预示任何方向。 开 MT5 把 Variant 改成 3 试一次,你能直接验证只有 CVariant3 被加载,其余子类根本不占运行时开销。

MQL5 / C++
CVariant1 var; class=class="str">"cmt">// 加载类来检查">" 条件
CCheckVariant* var;
class=class="str">"cmt">// 变量数量
class="type">int Variant=class="num">5;
class=class="str">"cmt">// 依据变量数量,五分之一子类将会被使用
class="kw">switch(Variant)
{
    case class="num">1:
var = new CVariant1;
      break;
    case class="num">2:
var = new CVariant2;
      break;
    case class="num">3:
var = new CVariant3;
      break;
    case class="num">4:
var = new CVariant4;
      break;
    case class="num">5:
var = new CVariant5;
      break;
}
class="type">bool rv = var.CheckVariant(class="num">1,class="num">2);

private 与 protected 在继承链上的真实边界

写 EA 或指标类库时,public 区放的是给调用方用的接口,这一点没有歧义。但 protected 和 private 对外层用户(也就是用你封装好的类写策略的人)表现完全一致——两者里的函数都不会出现在 MT5 代码补全的下拉列表里,用户根本调不到。 真正分水岭在继承。基类 CBase 里一个函数放 protected、一个放 private,子类 Class 想直接调用:ProtectedFunc() 编译通过,PrivateFunc() 会直接报“不能调用私有成员函数”。也就是说,private 不只是挡用户,连子类都挡;protected 只挡用户,不挡子类。 随文给了两个验证文件:OOP_sProtPriv_1.mq5 看用户视角下 public 仅暴露一个函数,OOP_sProtPriv_2.mq5 看子类调用两种分区的差异。图 4 用蓝/灰箭头标了基类函数对子类的可见性,蓝可用、灰不可用。 别把正态当圣经 封装层级不是越 private 越安全。若你打算让类库后续被扩展(比如写一系列外汇品种过滤器子类),基类核心逻辑放 protected 才合理;纯内部状态机放 private。先想清楚要不要给人继承,再定分区。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|  受保护的关键字的类                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------
class CName1
  {
class="kw">protected:
   class="type">int ProtectedFunc(class="type">int aArg)
     {
      class="kw">return(aArg);
     }
class="kw">public:
   class="type">int PublicFunction(class="type">int aArg)
     {
      class="kw">return(ProtectedFunc(aArg));
     }
  };
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|  类的私有关键字                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------
class CName2
  {
class="kw">private:
   class="type">int PrivateFunc(class="type">int aArg)
     {
      class="kw">return(aArg);
     }
class="kw">public:
   class="type">int PublicFunction(class="type">int aArg)
     {
      class="kw">return(PrivateFunc(aArg));
     }
  };
CName1 c1; class=class="str">"cmt">// 加载保护类
CName2 c2; class=class="str">"cmt">// 加载私有类
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|  基本类                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------
class CBase
  {
class="kw">protected:
   class="type">class="kw">string ProtectedFunc()
     {
      class="kw">return("CBase ProtectedFunc");
     }
class="kw">private:
   class="type">class="kw">string PrivateFunc()
     {
      class="kw">return("CBase PrivateFunc");
     }
class="kw">public:
   class="kw">virtual class="type">class="kw">string PublicFunction()
     {
      class="kw">return("");
     }
  };
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|  子类                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------
class Class: class="kw">public CBase
  {
class="kw">public:
   class="type">class="kw">string PublicFunction()
     {
      class=class="str">"cmt">// 带此行,所有代码会编译成功
      class="kw">return(ProtectedFunc());
      class=class="str">"cmt">// 如果您未注释此行和注释前一个, 将会遇到编译错误,返回(PrivateFunc());
      class=class="str">"cmt">// 返回(PrivateFunc());
     }
  };

「虚函数不重写就回退基类」

在 MQL5 的继承链里,基类声明的 virtual 函数并不强制子类重写。子类若提供了同名函数,调用时走子类版本;若没写,就静默回退到基类的实现——这对写 EA 框架时减少重复代码很实用。 上面这段示例里,Class2 完全是空壳,只继承了 CBase。执行 OnStart 时,c1.Function() 返回 "Function of child ",而 c2.Function() 返回 "Function of base class",证明空子类也能通过基类虚函数跑通逻辑。 从类使用者角度看,public 区的函数全部可见,这就是继承性的直观体现。但注意:子类自己加的“额外”函数,只有在指针类型声明为子类时才调得到;用基类指针载子类,那些额外函数会被编译器隐藏。 随附的 OOP_sDefaultVirtual_1.mq5 可直接在 MT5 里编译运行,改一下 Class1 的返回串就能验证多态替换是否生效。外汇与贵金属杠杆高,策略代码逻辑错一处可能瞬间放大亏损,跑通前务必用模拟盘验。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|  基本类                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------
class CBase
  {
class="kw">public:
   class="kw">virtual class="type">class="kw">string Function()
     {
      class="type">class="kw">string str="";
      str="Function ";
      str=str+"of base ";
      str=str+"class";
      class="kw">return(str);
     }
  };
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|  子类1                                                             |
class=class="str">"cmt">//+------------------------------------------------------------------
class Class1: class="kw">public CBase
  {
class="kw">public:
   class="type">class="kw">string Function()
     {
      class="type">class="kw">string str="";
      str="Function ";
      str=str+"of child ";
      class="kw">return(str);
     }
  };
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|  子类2                                                             |
class=class="str">"cmt">//+------------------------------------------------------------------
class Class2: class="kw">public CBase
  {
  };
Class1 c1; class=class="str">"cmt">// 加载类 class="num">1
Class2 c2; class=class="str">"cmt">// 加载类 class="num">2
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|                                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void OnStart()
  {
   Alert("class="num">1: "+c1.Function()); class=class="str">"cmt">// 从 Class1 运行函数
   Alert("class="num">2: "+c2.Function()); class=class="str">"cmt">// 从 CBase运行函数
  }
class="type">void OnStart() 
   {
    Alert("class="num">1: " + c1.Function()); class=class="str">"cmt">// 从 Class1运行函数
    Alert("class="num">2: " + c2.Function()); class=class="str">"cmt">// 从 CBase运行函数
   }

◍ 基类指针怎么决定跑哪个类

在 MQL5 里写带虚函数的继承结构时,指针类型直接决定了实际跑的是基类还是子类代码。如果你明确知道要用 Class1 还是 Class2,直接声明对应子类的实例最省事,虚函数调用会精确落到子类实现里。 不确定用哪个子类时,可以声明一个 CBase* 动态指针,运行时用 new 指向具体子类来载入。这样同一段调用代码,在 new Class1 和 new Class2 时行为不同,是多态的典型用法。 如果写成 CBase c; 这种自动(栈上)指针,编译器原样按基类处理,虚函数会被降格成普通函数,不会跳到子类。也就是说,想靠虚函数做运行时分发,必须用堆分配的动态指针,栈对象吃不到多态。 下面这段演示了三种声明差异:定长子类对象、基类动态指针、基类自动对象。注意 c=new Class1 之后,c 的虚函数走的是 Class1 逻辑;而 CBase c; 那种写法虚函数已静态绑定。

MQL5 / C++
Class1 c1; class=class="str">"cmt">// 加载类 class="num">1
Class2 c2; class=class="str">"cmt">// 加载类 class="num">2
CBase *c; class=class="str">"cmt">// 动态指针 
class="type">void OnStart() 
   {
     c=new Class1; class=class="str">"cmt">// 加载类
     ...
CBase c; class=class="str">"cmt">// 自动指针

用基类引用让一个函数吃下所有对象

在 MT5 的 MQL5 里,对象不能按值传参,指针必须加 & 以引用方式进函数。只要函数形参写成基类类型(例如 CBase &c),子类实例都能喂进去,函数内部再调虚函数,就能自动走到各自实现。 上面这段示例里,CBase 只声明了一个返回空串的虚 Function(),Class1 和 Class2 各自重写返回 "Class 1" / "Class 2"。OnStart 中连续调用 Function(c1) 和 Function(c2),弹窗会先后出现 Class 1、Class 2 两行——说明同一个函数处理了两种对象。 实盘写 EA 时,这种写法适合把不同信号源(均线类、波动率类)统一丢给一个研判函数。外汇和贵金属波动剧烈、杠杆高风险大,任何信号都只是概率倾向,别把基类多态当成胜率保证。 代码逐行看:class CBase 定义基类,virtual string Function() 留空实现;Class1: public CBase 重写返回具体标识;Class1 c1; Class2 c2; 是栈上实例化。void Function(CBase &c) 的 & 不能省,否则编译报错。里面 Alert(c.Function()) 利用虚表分发。OnStart 里两次 Function 调用验证了引用传参的多态效果。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|  基本类                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------
class CBase
  {
class="kw">public:
   class="kw">virtual class="type">class="kw">string Function()
     {
       class="kw">return("");
     }
  };
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|  子类1                                                             |
class=class="str">"cmt">//+------------------------------------------------------------------
class Class1: class="kw">public CBase
  {
class="kw">public:
   class="type">class="kw">string Function()
     {
       class="kw">return("Class class="num">1");
     }
  };
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|  子类2                                                             |
class=class="str">"cmt">//+------------------------------------------------------------------
class Class2: class="kw">public CBase
  {
class="kw">public:
   class="type">class="kw">string Function()
     {
       class="kw">return("Class class="num">2");
     }
  };
Class1 c1; class=class="str">"cmt">// 加载类 class="num">1
Class2 c2; class=class="str">"cmt">// 加载类 class="num">2
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|  处理对象的函数                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void Function(CBase &c)
  {
   Alert(c.Function());
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|                                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void OnStart()
  {
class=class="str">"cmt">// 处理对象使用一个函数.
   Function(c1);
   Function(c2);
  }

「类接口里的方法和属性到底怎么分」

写 MQL5 类的时候,圈内人嘴上说「方法」其实和「函数」是一回事,区别只在视角。你自己写类,public 区里声明的全是函数;但别人调你的类,敲个点弹出的下拉列表里那些能直接用的,就被叫成方法。 除了方法,public 区还能塞变量,包括数组。这些变量在下拉列表里和方法并排出现,术语上叫「属性」。属性和普通变量用法没两样,赋值、读取都直接走点号。 下面这段来自 OOP_sMethodsAndProperties.mq5 的示例,定义了两个 int 属性 Property1、Property2 和两个空方法 Function1、Function2,OnStart 里给属性赋值后弹窗读出。外汇与贵金属 EA 开发中滥用 public 属性会放大耦合风险,建议只暴露必要接口。 代码逐行看:class 开头定义类 CMethodsAndProperties;public: 之后 Property1、Property2 是整型属性并带注释;Function1、Function2 是返回 void 的空方法,内部仅 return;OnStart 中 c.Property1=1、c.Property2=2 完成属性写入,Alert 拼接 IntegerToString 输出两属性值。

MQL5 / C++
class CMethodsAndProperties 
   {
    class="kw">public:
      class="type">int               Property1; class=class="str">"cmt">// 属性 class="num">1
      class="type">int               Property2; class=class="str">"cmt">// 属性 class="num">2
      class="type">void Function1() 
           {
           class=class="str">"cmt">//...
           class="kw">return;
           }
      class="type">void Function2() 
           {
           class=class="str">"cmt">//...
           class="kw">return;
           }
   };
class="type">void OnStart() 
   {
   c.Property1 = class="num">1; class=class="str">"cmt">// 设置属性 class="num">1
   c.Property2 = class="num">2; class=class="str">"cmt">// 设置属性 class="num">2
   class=class="str">"cmt">// 读属性
   Alert("Property1 = " + IntegerToString(c.Property1) + ", Property2 = " + IntegerToString(c.Property2));
   }

◍ struct 怎么在 MT5 里落地

MQL5 里的 struct 比 class 轻,它只能装变量和数组,没有 public/private 之分,所有成员默认公开。写法是 struct 打头,大括号里直接声明成员,例如随附的 OOP_Struct.mq5 里就用了 Str1 装 int、double 和对应数组。 要用结构得先按结构名声明变量,也能声明结构数组:Str1 s1 是单实例,Str1 sar1[] 是数组,后者在策略里批量缓存不同品种的状态很顺手。 结构还能嵌套结构。上面 Str2 里塞了一个 Str1 类型的 Str 成员,想取里层变量得用两个点连写:s2.Str.IntVar=1 就是把 Str2 实例 s2 内嵌 Str1 的 IntVar 设为 1。外汇和贵金属波动快,用嵌套 struct 组织多周期数据比散变量更不容易写错,但杠杆品种高风险,参数错了可能瞬间回撤。 下面这段是原文结构声明的可直接拷进 MT5 验证:

MQL5 / C++
class="kw">struct Str1 
   {
    class="type">int    IntVar;
    class="type">int    IntArr[];
    class="type">class="kw">double DblVar[];
    class="type">class="kw">double DblArr[];
   };
Str1 s1;
Str1 sar1[];
class="kw">struct Str2 
   {
    class="type">int    IntVar;
    class="type">int    IntArr[];
    class="type">class="kw">double DblVar[];
    class="type">class="kw">double DblArr[];
    Str1   Str;
   };
s2.Str.IntVar=class="num">1;

MQL5 类机制的关键落点

把前面两篇拆开的面向对象写法收敛一下,真正在 MT5 里写类时容易卡住的只有几个硬规则。class 关键字之后跟类名,大括号内必须切出 private、protected、public 三个访问区,变量和函数落错区就会导致编译期或运行期越权调用。 private 区的成员只在类内部可见,protected 允许子类复用,public 则全局可访问;这是权限边界,不是装饰。类外定义函数必须写成 ClassName::FunctionName() 的形式,否则链接器不认归属。 动态指针创建对象要用 new,程序退出前必须 delete,漏掉就会在 EA 重载时留下内存残影。子类声明写成 class Class : public CBase,虚函数靠 virtual 标记,子类同名即覆盖、否则回退基类实现。 一个容易忽略的点:类内声明变量不能就地赋值,初始化要交给构造函数或后续调用。结构本身也能嵌套数组和其他结构,这在组织行情快照时比裸数组省事。下面这段是最小骨架,直接贴进 MT5 编辑器能编译通过。

MQL5 / C++
class CName 
  {
class="kw">private:
class="kw">protected:
class="kw">public:
  };
class="type">void ClassName::FunctionName() { ... }
class Class : class="kw">public CBase { ... }

「把工具请下神坛」

这套 OOP 示例里,OOP_CDeleteOrder_2.mqh 与 OOP_sDeleteOrders_3.mq5 是仅有的两个建议留下反复用的文件,其余 14 个演示脚本和包含文件在跑通逻辑后都能直接删,不占 Include 和 Scripts 目录。 真正写 EA 或脚本时,你大概率只会从构造函数传参删单那个类改起,而不是把 protected/private 的对照示例都留着。MT5 里打开 Scripts 夹拖一个 OOP_sConstDestr_1 进去编译,就能亲眼看到构造和析构的触发顺序,比看说明直观。 外汇和贵金属杠杆高、滑点凶,用这些类封装下单逻辑只是降低代码混乱度,不解决胜率问题;跑之前先在策略测试器用历史数据验证一遍参数传递有没有漏。

常见问题

用封装好的批量删单类,在构造时传入过滤条件,调用清理入口即可一次性移除符合条件的挂单。
把启停逻辑写进类的构造函数和析构函数,对象创建即开始监控,对象销毁即结束清理。
可以,小布能读你的代码结构并标出批量删单类的调用入口和构造析构配对,提醒可能泄漏或误删的地方。
分别用不同参数实例化两个类对象,各自独立管理一批挂单,互不干扰。
在析构里遍历数组逐个delete指针并清空,别只清数组不释放对象,否则会漏内存。