从基础到中级:继承·综合运用
(3/3)·多数人以为继承只属于类与面向对象,其实结构化编程里也能抽走重复、给后期维护减负
不少交易者写 EA 时习惯性复制粘贴结构体字段,等到要改一处共同逻辑,才发现散落在五六个地方。继承并不是面向对象语言的专利,把共有部分抽出来,是降低维护成本的直接手段。
用结构体搭一个可检索的键值容器
在 MT5 的 EA 或指标里,若要把多组「ID + 描述」暂存起来随时翻查,可以派生一个轻量结构体。下面这段从 st_Base 继承出 st_Dev01,私有成员 ValueInfo 存文本,外部只通过 SetKeyInfo 写、GetInfo 读,封装边界很清楚。 st_List 则是容器本体:内部用动态数组 List[] 装 st_Dev01,nElements 记当前容量。注意 AddList 里首次压入时 nElements 从 0 跳到 2,之后每次 +1,相当于首块预留了冗余位,ArrayResize 按此值扩数组——这种写法在回测中可减少前几次插入的重复分配次数。 SearchKey 用 for 从下标 1 开始扫,命中 GetKey()==arg 就返回该元素,找不到回退 List[0]。由于下标 0 在 AddList 逻辑里实际是空槽或末位,调用方得自己判断返回值是否真命中,否则可能拿到占位结构。 别把空槽当有效数据 List[0] 在没命中时充当「空结构」返回,但你的 st_Dev01 若没初始化 ValueInfo,GetInfo() 会给出空串。搜索前应先确认 nElements>1,或给 List[0] 打一个专属 Key 标记,避免把「未命中」误判成「Key=0 的记录」。
class="kw">struct st_Dev01 : class="kw">public st_Base { class="kw">private : class="type">class="kw">string ValueInfo; class="kw">public : class=class="str">"cmt">//+----------------+ class="type">void SetKeyInfo(class="type">uint arg1, class="type">class="kw">string arg2) { SetKey(arg1); ValueInfo = arg2; } class=class="str">"cmt">//+----------------+ class="type">class="kw">string GetInfo(class="type">void) { class="kw">return ValueInfo; } class=class="str">"cmt">//+----------------+ }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">struct st_List { class="kw">private : st_Dev01 List[]; class="type">uint nElements; class="kw">public : class=class="str">"cmt">//+----------------+ class="type">void Clear(class="type">void) { ArrayFree(List); nElements = class="num">0; } class=class="str">"cmt">//+----------------+ class="type">bool AddList(const st_Dev01 &arg) { nElements += (nElements == class="num">0 ? class="num">2 : class="num">1); ArrayResize(List, nElements); List[nElements - class="num">1] = arg; class="kw">return true; } class=class="str">"cmt">//+----------------+ st_Dev01 SearchKey(const class="type">uint arg) { for (class="type">uint c = class="num">1; c < nElements; c++) if (List[c].GetKey() == arg) class="kw">return List[c]; class="kw">return List[class="num">0]; } class=class="str">"cmt">//+----------------+ };
「用结构体继承搭一个可检索名单」
这段示例演示了如何在 MT5 里用基类加派生结构体的方式,组织一组带索引键的字符串数据。核心思路是:基类 st_Base 管键值,派生类 st_Dev01 在继承键值的基础上再挂一个字符串字段,方便后续按索引查找。 宏 PrintX 把变量名和值拼成可读输出,例如调用 PrintX(list.SearchKey(2).GetInfo()) 时,终端会打印索引 2 对应的名字。示例里 Names 数组写了 4 个名字,循环从 c=0 跑到 c=3,逐个 SetKeyInfo(c, Names[c]) 后塞进 st_List。 代码里基类把 KeyValue 设为 private,只暴露 SetKey 与 GetKey,派生类无法直接改键值,只能走接口。这种封装在写指标或 EA 的配置表时有用——你改底层存储逻辑不会波及上层调用。 开 MT5 新建脚本,把这段抄进 include 的 File 01.mqh 与主文件,跑完看终端是否输出 Carlos Almeida(索引 2)。外汇与贵金属杠杆高,实盘前务必先在策略测试器验证逻辑。
class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#include "Tutorial\File class="num">01.mqh" class="macro">#define PrintX(X) Print(class="macro">#X, " => [", X, "]") class="type">void OnStart(class="type">void) { const class="type">class="kw">string Names[] = { "Daniel Jose", "Edimarcos Alcantra", "Carlos Almeida", "Yara Alves" }; st_List list; for (class="type">uint c = class="num">0; c < Names.Size(); c++) { st_Dev01 info; info.SetKeyInfo(c, Names[c]); list.AddList(info); } PrintX(list.SearchKey(class="num">2).GetInfo()); } class="macro">#class="kw">property copyright "Daniel Jose" 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;
◍ 用模板结构体管好自己的键值列表
MQL5 里用 template<typename T> 写泛型结构体,能把「带 key 的对象集合」封装成可复用组件。下面这段 st_List 就是典型:私有成员放动态数组 List[] 和元素计数 nElements,对外只暴露 Clear、AddList、SearchKey 三个方法。 AddList 的扩容逻辑有个细节:首次插入时 nElements 从 0 跳到 2(预留一个空位),之后每次只加 1。这意味着数组下标 0 长期空置,SearchKey 查不到目标时直接 return List[0],相当于拿空槽当「未命中」哨兵,省了空指针判断。 代码里 T 必须自带 GetKey() 和 SetKey(),否则编译期就报错。你在 MT5 里新建 EA 把这段贴进 include 头文件,随便写个带 uint key 的小类就能跑通——外汇和贵金属杠杆高,回测多品种前先确认数组越界保护,实盘风险自行评估。
class="kw">public: class=class="str">"cmt">//+----------------+ class="type">void SetKeyInfo(class="type">uint arg1, class="type">class="kw">string arg2) { SetKey(arg1); ValueInfo = arg2; } class=class="str">"cmt">//+----------------+ class="type">class="kw">string GetInfo(class="type">void) { class="kw">return ValueInfo; } class=class="str">"cmt">//+----------------+ }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template <class="kw">typename T> class="kw">struct st_List { class="kw">private : T List[]; class="type">uint nElements; class="kw">public : class=class="str">"cmt">//+----------------+ class="type">void Clear(class="type">void) { ArrayFree(List); nElements = class="num">0; } class=class="str">"cmt">//+----------------+ class="type">bool AddList(const T &arg) { nElements += (nElements == class="num">0 ? class="num">2 : class="num">1); ArrayResize(List, nElements); List[nElements - class="num">1] = arg; class="kw">return true; } class=class="str">"cmt">//+----------------+ T SearchKey(const class="type">uint arg) { for (class="type">uint c = class="num">1; c < nElements; c++) if (List[c].GetKey() == arg) class="kw">return List[c]; class="kw">return List[class="num">0]; } class=class="str">"cmt">//+----------------+ }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">public: class=class="str">"cmt">//+----------------+ class="type">void SetKeyInfo(class="type">uint arg1, class="type">class="kw">string arg2) { SetKey(arg1); ValueInfo = arg2; } class=class="str">"cmt">//+----------------+ class="type">class="kw">string GetInfo(class="type">void) { class="kw">return ValueInfo; } class=class="str">"cmt">//+----------------+ }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template <class="kw">typename T> class="kw">struct st_List { class="kw">private : T List[]; class="type">uint nElements; class="kw">public : class=class="str">"cmt">//+----------------+ class="type">void Clear(class="type">void) { ArrayFree(List); nElements = class="num">0; } class=class="str">"cmt">//+----------------+ class="type">bool AddList(const T &arg) { nElements += (nElements == class="num">0 ? class="num">2 : class="num">1); ArrayResize(List, nElements); List[nElements - class="num">1] = arg; class="kw">return true; } class=class="str">"cmt">//+----------------+ T SearchKey(const class="type">uint arg) { for (class="type">uint c = class="num">1; c < nElements; c++) if (List[c].GetKey() == arg) class="kw">return List[c]; class="kw">return List[class="num">0]; } class=class="str">"cmt">//+----------------+ }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+
用模板结构给名单做键值索引
在 MT5 里做自定义数据容器时,先用一个模板基类把「键」隔离出来,能让后续多种数据结构复用同一套存取逻辑。下面这段把 KEY 类型参数化,结构体里只留一个私有成员 KeyValue,外部通过 SetKey / GetKey 读写,不暴露内部状态。 具体落地时,我们建一个 st_Dev01 继承 st_Base<uint>,额外挂一个字符串字段存姓名。主程序侧声明 Names 数组共 4 个元素(Daniel Jose、Edimarcos Alcantra、Carlos Almeida、Yara Alves),循环里用下标 0~3 调 SetKeyInfo 写进链表。 跑完之后 PrintX(list.SearchKey(2).GetInfo()) 会输出 Carlos Almeida 对应的记录——说明按整数键检索第 3 条(索引从 0 计)是通的。外汇与贵金属行情波动剧烈、杠杆风险高,这类工具仅用于本地数据组织验证,不代表任何交易信号。 别把继承当累赘 模板基类看着多写几行,实则把键类型从具体结构里抽掉,下次换 string 做键不用改存取代码,直接 st_Base<string> 就行。
class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "Tutorial\File class="num">01.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define PrintX(X) Print(class="macro">#X, " => [", X, "]") class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart(class="type">void) { const class="type">class="kw">string Names[] = { "Daniel Jose", "Edimarcos Alcantra", "Carlos Almeida", "Yara Alves" }; st_List <st_Dev01> list; for (class="type">uint c = class="num">0; c < Names.Size(); c++) { st_Dev01 info; info.SetKeyInfo(c, Names[c]); list.AddList(info); } PrintX(list.SearchKey(class="num">2).GetInfo()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template <class="kw">typename KEY> class="kw">struct st_Base { class="kw">private : KEY KeyValue; class="kw">public : class=class="str">"cmt">//+----------------+ class="type">void SetKey(const KEY arg) { KeyValue = arg; } class=class="str">"cmt">//+----------------+ KEY GetKey(class="type">void) { class="kw">return KeyValue; } class=class="str">"cmt">//+----------------+ }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template <class="kw">typename KEY> class="kw">struct st_Dev01 : class="kw">public st_Base <KEY> { class="kw">private : class="type">class="kw">string ValueInfo;
「用模板结构管价格键值对」
在 MT5 的 EA 或指标里,若要把每根 K 线的某些属性(比如时间、OHLC、自定义标记)随取随用,直接写裸数组很容易乱。下面这段模板把「单条记录」和「记录集合」拆成两个 struct,编译一次就能套用在任意数据类型上。 先看一下单条记录的写法:它公开了两个方法,SetKeyInfo 接收键和描述串,把键交给 SetKey 存起来,把说明写进 ValueInfo;GetInfo 则直接返回那段说明文本。这样你查某根 Bar 的时候,不只拿到数值,还能带回当时附带的注释。 集合部分用 st_List 模板承载。Clear 调 ArrayFree 释放数组并把计数归零;AddList 有意思——首次塞数据预留 2 个槽,之后每次只加 1,等于用一次多分配来摊薄频繁 resize 的开销。SearchKey 从下标 1 开始遍历,命中返回该记录,没命中返回 List[0](通常是空壳),调用方得自己判断是不是有效结果。 外汇和贵金属行情跳空多,这类键值结构用来缓存会话内高低点可能比全局变量稳,但高频调用仍要留意 ArrayResize 带来的微小延迟,实盘前建议在策略测试器跑一遍 10 万次插入看耗时。
class="kw">public: class=class="str">"cmt">//+----------------+ class="type">void SetKeyInfo(KEY arg1, class="type">class="kw">string arg2) { SetKey(arg1); ValueInfo = arg2; } class=class="str">"cmt">//+----------------+ class="type">class="kw">string GetInfo(class="type">void) { class="kw">return ValueInfo; } class=class="str">"cmt">//+----------------+ }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template <class="kw">typename T, class="kw">typename KEY> class="kw">struct st_List { class="kw">private : T List[]; class="type">uint nElements; class="kw">public : class=class="str">"cmt">//+----------------+ class="type">void Clear(class="type">void) { ArrayFree(List); nElements = class="num">0; } class=class="str">"cmt">//+----------------+ class="type">bool AddList(const T &arg) { nElements += (nElements == class="num">0 ? class="num">2 : class="num">1); ArrayResize(List, nElements); List[nElements - class="num">1] = arg; class="kw">return true; } class=class="str">"cmt">//+----------------+ T SearchKey(const KEY arg) { for (class="type">uint c = class="num">1; c < nElements; c++) if (List[c].GetKey() == arg) class="kw">return List[c]; class="kw">return List[class="num">0]; } class=class="str">"cmt">//+----------------+ };
◍ 用模板结构体给名单做键值索引
在 MT5 里想给一组字符串配一个整数索引,最干净的做法是把键值逻辑封进模板结构体,而不是在 OnStart 里写一堆数组下标硬判断。下面这段把姓名数组映射成 0~3 的 uint 键,塞进自定义链表后按 Key 反查,输出的是第 2 号位对应的 Carlos Almeida。
注意第 06 行定义的 PrintX 宏:它把变量名和值拼成 变量名 => [值] 的格式,调试模板嵌套时比裸 Print 直观得多。跑完脚本,终端会打印 list.SearchKey(2).GetInfo() => [Carlos Almeida],说明键值 2 确实绑到了数组第三项。
基类 st_Base 只干一件事——存 KeyValue 并提供 SetKey / GetKey。把它写成 template <typename KEY> 后,键类型由调用方决定;本例用 #define def_TypeKey uint 把 uint 注入,列表和结构体都跟着用这个别名,后期换 long 只改一行。外汇与贵金属 EA 开发中这类泛型封装能降低耦合,但回测前务必在策略测试器跑一遍,模板实例化错误只在编译后运行才暴露,属高频踩坑点。
class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "Tutorial\File class="num">01.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define PrintX(X) Print(class="macro">#X, " => [", X, "]") class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart(class="type">void) { class="macro">#define def_TypeKey class="type">uint const class="type">class="kw">string Names[] = { "Daniel Jose", "Edimarcos Alcantra", "Carlos Almeida", "Yara Alves" }; st_List <st_Dev01 <def_TypeKey>, def_TypeKey> list; for (class="type">uint c = class="num">0; c < Names.Size(); c++) { st_Dev01 <def_TypeKey> info; info.SetKeyInfo(c, Names[c]); list.AddList(info); } PrintX(list.SearchKey(class="num">2).GetInfo()); class="macro">#undef def_TypeKey } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template <class="kw">typename KEY> class="kw">struct st_Base { class="kw">private : KEY KeyValue; class="kw">public : class=class="str">"cmt">//+----------------+ class="type">void SetKey(const KEY arg) { KeyValue = arg; } class=class="str">"cmt">//+----------------+ KEY GetKey(class="type">void) { class="kw">return KeyValue; } class=class="str">"cmt">//+----------------+ }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template <class="kw">typename KEY, class="kw">typename INFO>
用模板结构体封装键值对与动态列表
在 MT5 的 EA 或指标里管理多品种参数时,散落的单变量很容易失控。下面这套结构体把「键-值」绑定和「动态数组列表」拆成了两层,编译期就能确定类型,运行期省掉反复 ArrayResize 的碎片开销。 st_Dev01 继承 st_Base<KEY>,私有成员 ValueInfo 存附加信息,公开方法 SetKeyInfo 同时写键与信息,GetInfo 回读。注意 nElements 从 0 起算,但 AddList 首次插入时直接跳到 2,意味着 List[0] 被保留作「未命中」默认返回位,真实数据从下标 1 开始。 st_List 的 SearchKey 循环从 c=1 跑起,命中返回对应元素,未命中返回 List[0]——这个空壳位必须在 Clear 或构造时保证存在,否则返回野引用。外汇与贵金属波动剧烈、杠杆高风险大,这类容器只解决组织问题,不替代风控。 把下面代码贴进 MT5 编辑器,改 KEY/INFO 类型即可接自己的品种映射表;跑一遍 AddList 后打印 SearchKey 返回值,验证 List[0] 兜底逻辑是否符合预期。
class="kw">struct st_Dev01 : class="kw">public st_Base <KEY> { class="kw">private : INFO ValueInfo; class="kw">public : class=class="str">"cmt">//+----------------+ class="type">void SetKeyInfo(KEY arg1, INFO arg2) { SetKey(arg1); ValueInfo = arg2; } class=class="str">"cmt">//+----------------+ INFO GetInfo(class="type">void) { class="kw">return ValueInfo; } class=class="str">"cmt">//+----------------+ }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template <class="kw">typename T, class="kw">typename KEY> class="kw">struct st_List { class="kw">private : T List[]; class="type">uint nElements; class="kw">public : class=class="str">"cmt">//+----------------+ class="type">void Clear(class="type">void) { ArrayFree(List); nElements = class="num">0; } class=class="str">"cmt">//+----------------+ class="type">bool AddList(const T &arg) { nElements += (nElements == class="num">0 ? class="num">2 : class="num">1); ArrayResize(List, nElements); List[nElements - class="num">1] = arg; class="kw">return true; } class=class="str">"cmt">//+----------------+ T SearchKey(const KEY arg) { for (class="type">uint c = class="num">1; c < nElements; c++) if (List[c].GetKey() == arg) class="kw">return List[c]; class="kw">return List[class="num">0]; } class=class="str">"cmt">//+----------------+ };
「用模板链表按职位反查姓名」
这段脚本演示了如何在 MT5 里用泛型链表结构,把「职位」当键、「姓名」当值做检索。核心依赖前两篇封装的 st_List 与 st_Dev01,本篇只负责组装数据并验证查找逻辑。 先在 OnStart 里用 #define 把键和值都限定为 string,再声明一个 4 行 2 列的 Names 数组,依次填入 Daniel Jose / Chief Programmer、Edimarcos Alcantra / Programmer 等 4 组样本。循环里把每行拆成 st_Dev01 节点,SetKeyInfo 把职位存为键、姓名存为值,再 AddList 挂进链表。 检索部分直接调 SearchKey:传入 "Chief Programmer" 能回吐 "Daniel Jose";传入不存在的 "Guest" 则得到空值。这种写法让你在 EA 里维护动态通讯录或参数映射时,不必硬编码数组下标,扩展行数只改 Names 即可。 外汇与贵金属市场波动剧烈、杠杆风险高,这类工具仅用于提升代码可维护性,不构成任何交易信号。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "Tutorial\File class="num">01.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define PrintX(X) Print(class="macro">#X, " => [", X, "]") class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart(class="type">void) { class="macro">#define def_TypeKey class="type">class="kw">string class="macro">#define def_TypeInfo class="type">class="kw">string const class="type">class="kw">string Names[][class="num">2] = { "Daniel Jose" , "Chief Programmer", "Edimarcos Alcantra", "Programmer", "Carlos Almeida" , "Junior Programmer", "Yara Alves" , "Accounting" }; st_List <st_Dev01 <def_TypeKey, def_TypeInfo>, def_TypeKey> list; for (class="type">int c = class="num">0; c < ArrayRange(Names, class="num">0); c++) { st_Dev01 <def_TypeKey, def_TypeInfo> info; info.SetKeyInfo(Names[c][class="num">1], Names[c][class="num">0]); list.AddList(info); } PrintX(list.SearchKey("Chief Programmer").GetInfo()); PrintX(list.SearchKey("Guest").GetInfo()); class="macro">#undef def_TypeKey class="macro">#undef def_TypeInfo } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 画得少,看得清
这套系列把 OOP 的表面光环拆开,露出的底子是结构化编程:很多被当成面向对象专属的能力,其实在普通函数与过程里就能跑通。回看前几篇的附录代码,2.52 KB 的 ZIP 里塞了可直接在 MT5 编译的示例,与其反复争论范式,不如把每段逻辑抄进自己的 EA 里改两行参数。 外汇与贵金属杠杆高、滑点随机,任何从代码结构推导出的信号都只是概率倾向,真要信得过,先开 MT5 用策略测试器跑满三年 tick 再说。 写到这,系列暂告一段落,但 MT5 终端不会等你——下一次你点开 MetaEditor,或许就该轮到自己写一套不那么花哨却跑得稳的东西了。