从基础到中级:继承·进阶篇
(2/3)· 以为继承只是面向对象专属?这篇把结构体里的复用机制拆给你看
用基类把异构数据收进同一套容器
在 MT5 的 EA 或指标工程里,经常要同时管「单值注册项」和「双值生物特征项」两类结构。如果各自写一套存取逻辑,后续遍历和序列化会非常散。 这里用 st_Base 作为公共基类,只放一个 private 的 uint KeyValue,再暴露 SetKey / GetKey。派生出来的 st_Reg 持有一个 string Value,st_Bio 持有 string Value[2],两者通过各自重载的 SetValue 把外部传入的参数写进成员。 关键点在 st_Bio::SetValue:它接收 arg1、arg2、arg3 三个参数,但只把 arg2 写进 Value[0]、arg3 写进 Value[1],arg1 在当前片段里未直接赋值给成员(可能留给基类 Key 用)。这种「同样的接口名、不同的参数列」就是编译期多态的入口。 开 MT5 新建一个 include 头文件,把下面代码贴进去编译,能验证结构体继承与数组成员初始化是否如预期。外汇与贵金属品种波动剧烈,这类底层封装虽不直接下单,但错误的数据结构会在回测中放大滑点偏差。
class="kw">struct st_Base { class="kw">private: class="type">uint KeyValue; class="kw">public: class="type">void SetKey(const class="type">uint arg) { KeyValue = arg; } class="type">uint GetKey(class="type">void) { class="kw">return KeyValue; } }; class="kw">struct st_Reg : class="kw">public st_Base { class="kw">private: class="type">class="kw">string Value; class="kw">public: class="type">void SetValue(const class="type">uint arg1, const class="type">class="kw">string arg2) { Value = arg2; } class="type">class="kw">string GetValue(class="type">void) { class="kw">return Value; } }; class="kw">struct st_Bio : class="kw">public st_Base { class="kw">private: class="type">class="kw">string Value[class="num">2]; class="kw">public: class="type">void SetValue(const class="type">uint arg1, const class="type">class="kw">string arg2, const class="type">class="kw">string arg3) { Value[class="num">0] = arg2; Value[class="num">1] = arg3; } };
◍ 用基类把注册与生物特征数据收拢
MQL5 里结构体也能玩继承。先定义一个 st_Base,把 KeyValue 藏进 private,只留 SetKey 和 GetKey 两个 public 接口(行 004–013),所有派生结构都自带这把 uint 钥匙。 st_Reg 继承 st_Base 后,private 里只放一个 string Value,SetValue 先调父类的 SetKey 写索引、再存字符串(行 021–025)。这是单值登记的写法,适合账号或标签类数据。 st_Bio 同样继承 st_Base,但 Value 是长度为 2 的 string 数组(行 033)。它的 SetValue 接收三个参数,前一个是 uint 钥匙,后两个分别落进 Value[0] 和 Value[1],比 st_Reg 多存一组关联信息。 注意另一段派生写法:GetValue 用两个 string 引用出参,把 Value[0]、Value[1] 一次性吐出去并返回 true(行 41–47)。在 MT5 里跑这段,你能直接验证数组长度 2 与双出参引用的行为,外汇贵金属品种做用户数据映射时别忘这类结构的高风险误用可能。
class="type">bool GetValue(class="type">class="kw">string &arg1, class="type">class="kw">string &arg2) { arg1 = Value[class="num">0]; arg2 = Value[class="num">1]; class="kw">return true; } class=class="str">"cmt">//+----------------+ }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">struct st_Base { class="kw">private: class="type">uint KeyValue; class="kw">public: class=class="str">"cmt">//+----------------+ class="type">void SetKey(const class="type">uint arg) { KeyValue = arg; } class=class="str">"cmt">//+----------------+ class="type">uint GetKey(class="type">void) { class="kw">return KeyValue; } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">struct st_Reg : class="kw">public st_Base { class="kw">private: class="type">class="kw">string Value; class="kw">public: class=class="str">"cmt">//+----------------+ class="type">void SetValue(const class="type">uint arg1, const class="type">class="kw">string arg2) { SetKey(arg1); Value = arg2; } class=class="str">"cmt">//+----------------+ class="type">class="kw">string GetValue(class="type">void) { class="kw">return Value; } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">struct st_Bio : class="kw">public st_Base { class="kw">private: class="type">class="kw">string Value[class="num">2]; class="kw">public: class=class="str">"cmt">//+----------------+ class="type">void SetValue(const class="type">uint arg1, const class="type">class="kw">string arg2, const class="type">class="kw">string arg3) {
「用模板结构存键值对与动态数组」
上面这段 MT5 代码展示了两类自定义结构:一个固定存两个字符串值的 st_Bio,以及一个泛型 st_Data<T> 动态容器。st_Bio 里 SetKey 把传入参数写成键,Value[0]、Value[1] 分别落地两个字段,GetValue 再按引用原样吐回,调用零拷贝。 st_Data 的 Set 方法用 ArrayResize 做增量扩容:当数组为空时一次开 2 个槽,否则每次加 1 个,返回 INVALID_HANDLE 时判定失败。实测在 MT5 编译环境下,首次插入后 Values.Size() 为 2,后续每次 +1,这个步进差异直接影响内存申请次数。 Get 方法按 index 遍历比对 GetKey(),命中即返回,未命中退回 Values[0]——这意味着查不到键时不会报错,而是静默给首个元素,写回测脚本时容易掩盖键值错配。 宏 PrintX 把变量名和值拼成 'X => [值]' 打到日志,CheckBio 则预置 string sz[2] 准备承接 Bio 的双字段,方便在策略初始化阶段肉眼核对结构体里到底写了什么。
SetKey(arg1); Value[class="num">0] = arg2; Value[class="num">1] = arg3; } class=class="str">"cmt">//+----------------+ class="type">bool GetValue(class="type">class="kw">string &arg1, class="type">class="kw">string &arg2) { arg1 = Value[class="num">0]; arg2 = Value[class="num">1]; class="kw">return true; } class=class="str">"cmt">//+----------------+ }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template <class="kw">typename T> class="kw">struct st_Data { class="kw">private: T Values[]; class="kw">public: class=class="str">"cmt">//+----------------+ 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; } class=class="str">"cmt">//+----------------+ T Get(const class="type">uint index) { for (class="type">uint c = class="num">0; c < Values.Size(); c++) if (Values[c].GetKey() == index) class="kw">return Values[c]; class="kw">return Values[class="num">0]; } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define PrintX(X) Print(class="macro">#X, " => [", X, "]") class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CheckBio(st_Data <st_Bio> &arg) { class="type">class="kw">string sz[class="num">2]; Print("Checking data in the structure...");
模板容器在 OnStart 里的实际装配
上面这段是脚本入口 OnStart 里把字符串数组和键值数组塞进两个模板实例的过程。T 被按空格切成 8 个子串,M 与 K 各 8 个元素,循环 c 从 0 到 7 依次调用 reg.SetValue 与 bio.SetValue,再把对象写进 Info_1(st_Reg 模板)和 Info_2(st_Bio 模板)。 注意 K 数组顺序是 {2,1,4,0,7,5,3,6},并不是连续下标,所以 Info_1.Get(3) 实际取到的是 K[3]=0 对应的 reg 记录,而 Info_1.Get(13) 越界,会走 PrintX 里的失败分支。 CheckBio(Info_2) 里那段 for 循环只跑 i=7 和 i=10 两次(步长 3,上限 11),分别打印索引 7、10 的二维数组 sz。若 GetValue 成功就 ArrayPrint,失败打 Failed。你在 MT5 里跑这段代码,终端大概率是先出两条 Info_1 的取值日志,再看到两屏 sz 数组或 Failed 提示。 外汇与贵金属品种做这类数据结构验证时,行情跳动和内存占用虽不直接相关,但 EA 里滥用模板容器会拖慢 tick 处理,实盘前务必在策略测试器用历史数据跑一遍确认无越界。
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; reg.SetValue(K[c], H[c]); bio.SetValue(K[c], M[c], H[c]); Info_1.Set(reg); Info_2.Set(bio); } PrintX(Info_1.Get(class="num">3).GetValue()); PrintX(Info_1.Get(class="num">13).GetValue()); CheckBio(Info_2); }
◍ 把结构体、模板和继承揉进同一个列表
MQL5 没有 C++ 全套语法,但把结构化、模板、继承三者叠在一起写并不算越界。很多人觉得这类代码神秘,其实只是少有人把已学过的概念串起来用——同样效果,别人写一大坨,你用编译器代劳就行。 下面这段是起点:一个带私有键值的基结构,Setter/Getter 包住 uint 类型的 KeyValue。它后面会被继承,也会被模板化,但此刻先单独看。
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) class="num">11. { class="num">12. KeyValue = arg; class="num">13. } class="num">14. class=class="str">"cmt">//+----------------+ class="num">15. class="type">uint GetKey(class="type">void) class="num">16. { class="num">17. class="kw">return KeyValue; class="num">18. } class="num">19. class=class="str">"cmt">//+----------------+ class="num">20. }; class="num">21. 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) class="num">11. { class="num">12. KeyValue = arg; class="num">13. } class="num">14. class=class="str">"cmt">//+----------------+ class="num">15. class="type">uint GetKey(class="type">void) class="num">16. { class="num">17. class="kw">return KeyValue; class="num">18. } class="num">19. class=class="str">"cmt">//+----------------+ class="num">20. }; class="num">21. class=class="str">"cmt">//+------------------------------------------------------------------+
「用继承把键值对封装进结构体」
在 MT5 的 EA 或指标里,如果你要把多个对象的标识和附加信息一起管起来,基类加派生类的写法能少写很多重复代码。下面这段把 uint 类型的 KeyValue 藏在 private 里,只暴露 SetKey 和 GetKey 两个公共接口。
class="kw">struct st_Base { class="kw">private : class="type">uint KeyValue; class="kw">public : class="type">void SetKey(const class="type">uint arg) { KeyValue = arg; } class="type">uint GetKey(class="type">void) { class="kw">return KeyValue; } };
class="kw">struct st_Dev01 : class="kw">public st_Base { class="kw">private : class="type">class="kw">string ValueInfo; class="kw">public : class="type">void SetKeyInfo(class="type">uint arg1, class="type">class="kw">string arg2) { SetKey(arg1); ValueInfo = arg2; } class="type">class="kw">string GetInfo(class="type">void) { class="kw">return ValueInfo; } };
class="kw">struct st_Base { class="kw">private : class="type">uint KeyValue; class="kw">public : class="type">void SetKey(const class="type">uint arg) { KeyValue = arg; } class="type">uint GetKey(class="type">void) { class="kw">return KeyValue; } }; class="kw">struct st_Dev01 : class="kw">public st_Base { class="kw">private : class="type">class="kw">string ValueInfo; class="kw">public : class="type">void SetKeyInfo(class="type">uint arg1, class="type">class="kw">string arg2) { SetKey(arg1); ValueInfo = arg2; } class="type">class="kw">string GetInfo(class="type">void) { class="kw">return ValueInfo; } };