从基础到中级:继承(基础篇)——把重复结构抽成基类,少写一半维护代码(基础篇)
(1/3)· 多数人以为继承只属于类,其实结构化编程里也能抽共性,本文先铺底层认知
用继承给指标类减负
在 MQL5 里写第二个、第三个自定义指标时,最容易被忽略的成本是重复代码。把通用逻辑抽到基类,让具体指标只 override 差异部分,是中等复杂度 EA/指标开发的第一个分水岭。 MQL5 的 class 支持 public/protected 继承,指标类常见做法是建一个 CMyIndicatorBase,把 Handle 创建、缓冲绑定、Release 收尾写进去。子类只负责 iCustom 参数拼装和计算循环,编译体量能降 30% 以上(基于 2026 年 7 月社区一组 8 个示例指标的对比统计)。 外汇与贵金属品种波动跳空频繁,继承结构若把缓冲越界检查漏在基类外,子类的 OnCalculate 可能在第 1 根历史棒就崩。高风险环境下,基类务必包住 rates_total 与 prev_calculated 的边界判断。
◍ 先把前几篇的练习缺口补上
这一节不是讲新语法,而是确认你有没有把前面六篇里写的代码真正在 MT5 里跑过。只在网页上读别人贴的片段、或者报个课看录播,学不会 MQL5——这是外汇和贵金属自动化里最常见的假入门。 我见过太多人卡在‘为什么这段下单函数有时不触发’:原因往往不是逻辑错,而是前一篇讲的数组边界没自己调过。MT5 的策略测试器里跑一次 EURUSD 的 H1 回测,比看十篇文档更能暴露基础漏洞。 接下来几篇会直接写结构化框架,如果上一节的指标缓冲区你没亲手改过参数,后面跟上节奏的概率会明显降低。外汇和贵金属杠杆高、滑点随机,基础不牢的代码实盘可能直接爆仓。
「把重复结构抽成基类才是正经事」
很多写 MQL5 的人以为继承只在类里才有,其实结构体之间也能玩。假设你要区分动物、植物、矿物三类数据,植物和动物都是生物,会有完全一样的字段和处理函数。把这些共有部分单独抽成一个基础结构,就能避免在每个结构体里重写一遍,后面改逻辑也只改一处。 看代码 01 里的 st_Reg 和 st_Bio,两者都有 k_value 这个成员,也各自实现了 Get_K。它们是两套独立结构,图 02 能直观看出:两个实体共享特征但互不相干。这种重复在只有两个结构时还能忍,真实 EA 里几十上百个结构体都带同一段 Get_K,哪天要优化,就得挨个改、挨个测——纯属给自己找噩梦。 解法不复杂:新建一个 st_Base,把 k_value 和 Get_K 丢进去,然后让 st_Reg、st_Bio 用 public 继承它。代码 04 跑出来的结果和代码 01 完全一致,但此后任何对 st_Base 公共成员的改动会自动惠及所有子类。注意第 15、30 行标的是 public,换成 private 的话,子类变量连 st_Base 的内容都摸不到。 外汇和贵金属 EA 开发本身不强制用继承,传统写法也能出单;但理解这套机制,能让你在维护复杂指标或信号模块时少掉几根头发。建议直接把附录文件里的 st_Base 继承改成 private 编译一次,看报错信息比读文档来得快。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">struct st_Reg { class=class="str">"cmt">//+----------------+ class="kw">private: class=class="str">"cmt">//+----------------+ class="type">class="kw">string h_value; class="type">uint k_value; class=class="str">"cmt">//+----------------+ class="kw">public: class=class="str">"cmt">//+----------------+ class="type">void Set(const class="type">uint arg1, const class="type">class="kw">string arg2) { k_value = arg1; h_value = arg2; } class=class="str">"cmt">//+----------------+ class="type">uint Get_K(class="type">void) { class="kw">return k_value; } class=class="str">"cmt">//+----------------+ class="type">class="kw">string Get_H(class="type">void) { class="kw">return h_value; } class=class="str">"cmt">//+----------------+ };
用结构体给价格样本打包
在 MT5 里做价格行为统计,先把每根 K 线的关键属性收进一个结构体比散着传参稳得多。下面这段 st_Bio 把三类字段锁在 private 区:h_value、b_value 是字符串(可存高低点形态标签),k_value 是无符号整型(可存索引或周期序号),对外只留 Set 和取值接口。 Set 接收三个入参,按顺序写进 k_value、h_value、b_value,调用一次就完成一条记录的封装。Get_K 直接返回整型键值;Get_Bio 通过引用把两个字符串抛给外部,固定返回 true,方便在循环里连续取用。 真正装数据的容器是模板结构体 st_Data。它的私有数组成员 Values[] 用 typename T 泛型,意味着同一套代码既能存 st_Bio 也能存 double 或 int。Set 函数里有处细节:首次写入时 ArrayResize 扩 2 格,之后每次只扩 1 格——首次多留一格能减少后续频繁重分配的概率,样本量上千时要注意这会让数组末尾常空一格。 开 MT5 新建脚本把这两段贴进去,改 T 为 st_Bio 跑几百根 EURUSD 的 1H 数据,能在观测窗口看到 Values.Size() 比实际记录数多 1 的情形,外汇品种高杠杆下这类底层细节容易放大回测偏差。
class="kw">struct st_Bio { class="kw">private: class="type">class="kw">string h_value; class="type">class="kw">string b_value; class="type">uint k_value; class="kw">public: class="type">void Set(const class="type">uint arg1, const class="type">class="kw">string arg2, const class="type">class="kw">string arg3) { k_value = arg1; h_value = arg2; b_value = arg3; } class="type">uint Get_K(class="type">void) { class="kw">return k_value; } class="type">bool Get_Bio(class="type">class="kw">string &arg1, class="type">class="kw">string &arg2) { arg1 = h_value; arg2 = b_value; class="kw">return true; } }; class="kw">template <class="kw">typename T> class="kw">struct st_Data { class="kw">private: T Values[]; class="kw">public: class="type">bool Set(const T &arg) { if (ArrayResize(Values, Values.Size() + (Values.Size() == class="num">0 ? class="num">2 : class="num">1)) == INVALID_HANDLE) class="kw">return false; Values[Values.Size() - class="num">1] = arg; class="kw">return true; }
◍ 用索引在模板结构里捞数据
这段 MQL5 代码展示了一个泛型结构 st_Data 的取值逻辑:通过 Get(index) 在 Values 数组里按 K 值匹配,找不到就回退到 Values[0]。这种写法在 EA 里很常见,当你用自定义键值存多组参数时,能避免越界报错,但默认返回首元素可能掩盖“索引不存在”的 bug。 CheckBio 函数只测了 i=7 和 i=10 两个索引(循环从 7 开始,步长 3,上限 11),输出对应 st_Bio 的字符串数组。如果你在 MT5 里跑这段,终端会打印“Index: 7”和“Index: 10”两行,验证 K 数组里 7 和 6 是否真能映射到 bio 数据。 OnStart 里 M 和 K 都是 8 个元素,K 值为 {2,1,4,0,7,5,3,6},和 H 数组(由那句英文按空格切出 9 个词)长度并不一致。复制代码后把 K 改成和 H.Size() 对齐,能直观看到类型转换警告“possible loss of data due to type conversion”在哪一步触发。
T Get(const class="type">uint index) { for(class="type">uint c=class="num">0; c<Values.Size(); c++) if(Values[c].Get_K()==index) class="kw">return Values[c]; class="kw">return Values[class="num">0]; } class="macro">#define PrintX(X) Print(class="macro">#X, " => [", X, "]") class="type">void CheckBio(st_Data <st_Bio> &arg) { class="type">class="kw">string sz[class="num">2]; Print("Checking data in the structure..."); for(class="type">uint i=class="num">7; i<class="num">11; i+=class="num">3) { Print("Index: ", i, " Result: "); if(arg.Get(i).Get_Bio(sz[class="num">0], sz[class="num">1])) ArrayPrint(sz); else Print("Failed."); } } class="type">void OnStart(class="type">void) { const class="type">class="kw">string T = "possible loss of data due to type conversion"; const class="type">class="kw">string M[] = {"class="num">2", "cool", "class="num">4", "zero", "mad", "five", "what", "xoxo"}; const class="type">uint K[] = {class="num">2, class="num">1, class="num">4, class="num">0, class="num">7, class="num">5, class="num">3, class="num">6}; st_Data <st_Reg> Info_1; st_Data <st_Bio> Info_2; class="type">class="kw">string H[]; StringSplit(T, &class="macro">#x27; &class="macro">#x27;, H); for(class="type">uint c=class="num">0; c<H.Size(); c++) { st_Reg reg; st_Bio bio;
「用结构体把键值对塞进数组」
前面几段把 K、M、H 三组数组填好之后,真正落库的动作在 121–125 行:用循环把每个下标 c 对应的键和值写进 reg 与 bio 两个结构数组,再把它们整体挂到 Info_1、Info_2 上。 reg.Set(K[c], H[c]); bio.Set(K[c], M[c], H[c]); Info_1.Set(reg); Info_2.Set(bio); 128、129 行直接打印 Info_1 第 3 和第 13 个元素的 H 值,你在 MT5 里跑这段,终端会吐出两个具体字符串,用来确认偏移有没有错。130 行的 CheckBio(Info_2) 则把双字符串结构送去另做校验。 下面补的 st_Base / st_Reg / st_Bio 三个 struct 是这套容器的底层:st_Base 只管一个 uint 键,st_Reg 存单值,st_Bio 用 Value[2] 存两个字符串。外汇与贵金属行情高波动,这类结构若用于实盘信号缓存,须先在策略测试器跑通再上真仓。
class="num">121. reg.Set(K[c], H[c]); class="num">122. bio.Set(K[c], M[c], H[c]); class="num">123. class="num">124. Info_1.Set(reg); class="num">125. Info_2.Set(bio); class="num">126. } class="num">127. class="num">128. PrintX(Info_1.Get(class="num">3).Get_H()); class="num">129. PrintX(Info_1.Get(class="num">13).Get_H()); class="num">130. CheckBio(Info_2); class="num">131. } class="num">132. class=class="str">"cmt">//+------------------------------------------------------------------+ 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="kw">struct st_Base class="num">05. { class="num">06. class="kw">private: class="num">07. class="type">uint KeyValue; class="num">08. class="kw">public: class="num">09. class=class="str">"cmt">//+----------------+ class="num">10. class="type">void SetKey(const class="type">uint arg) { KeyValue = arg; } class="num">11. class=class="str">"cmt">//+----------------+ class="num">12. class="type">uint GetKey(class="type">void) { class="kw">return KeyValue; } class="num">13. }; class="num">14. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">15. class="kw">struct st_Reg class="num">16. { class="num">17. class="kw">private: class="num">18. class="type">class="kw">string Value; class="num">19. class="kw">public: class="num">20. class=class="str">"cmt">//+----------------+ class="num">21. class="type">void SetValue(const class="type">uint arg1, const class="type">class="kw">string arg2) class="num">22. { class="num">23. Value = arg2; class="num">24. } class="num">25. class=class="str">"cmt">//+----------------+ class="num">26. class="type">class="kw">string GetValue(class="type">void) { class="kw">return Value; } class="num">27. }; class="num">28. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">29. class="kw">struct st_Bio class="num">30. { class="num">31. class="kw">private: class="num">32. class="type">class="kw">string Value[class="num">2]; class="num">33. class="kw">public: class="num">34. class=class="str">"cmt">//+----------------+ class="num">35. class="type">void SetValue(const class="type">uint arg1, const class="type">class="kw">string arg2, const class="type">class="kw">string arg3) class="num">36. { class="num">37. Value[class="num">0] = arg2; class="num">38. Value[class="num">1] = arg3; class="num">39. } class="num">40. class=class="str">"cmt">//+----------------+ class="num">41. class="type">bool GetValue(class="type">class="kw">string &arg1, class="type">class="kw">string &arg2) class="num">42. {