MQL5 中的电子表格·进阶篇
(2/3)· 直接裸写二维数组常陷类型混乱,这篇把 CTable 的方法与数据模型一次讲清
◍ 从日志抓出表格单元格的真实值
在 MT5 里用 CTable 这类自定义表结构时,最直接的验证方式就是往专家日志里打点。下面这句把 table1 第 2 列第 0 行的内容推到终端,能立刻看到该格存了什么。
Print("table1 ",table1.sGet(class="num">2,class="num">0)); class=class="str">"cmt">// print the cell located in the class="num">2-nd column, class="num">0 row of the &class="macro">#x27;table1&class="macro">#x27; table. } class="num">2011.02.class="num">09 class="num">14:class="num">18:class="num">37 Table Script(EURUSD,H1) table1 class="num">321.01232000 class="num">2011.02.class="num">09 class="num">14:class="num">18:class="num">37 Table Script(EURUSD,H1) table class="num">321.01232
Print("table1 ",table1.sGet(class="num">2,class="num">0)); class=class="str">"cmt">// print the cell located in the class="num">2-nd column, class="num">0 row of the &class="macro">#x27;table1&class="macro">#x27; table. } class="num">2011.02.class="num">09 class="num">14:class="num">18:class="num">37 Table Script(EURUSD,H1) table1 class="num">321.01232000 class="num">2011.02.class="num">09 class="num">14:class="num">18:class="num">37 Table Script(EURUSD,H1) table class="num">321.01232
「列模型还是行模型,存储结构怎么选」
在 MT5 里组织表格数据,常见有连接列和连接行两种方案。本文落地用的是列模型:通过一个中间物引用信息,上下范围的实施差异不大,但列模型允许把数据方法直接放在存储数据的下范围对象里。 若改用行模型,往往要覆盖上类 CTable 中处理信息的方法,类一旦需要增强就会变复杂。两种都能跑,列模型移动数据快,行模型逐行追加和取行更快——因为实盘数据本就是按行进表的。 还有种最省事的做法:直接用结构数组。它实现成本最低,但结构必须由程序表硬编码说明,等于放弃了用自定义参数动态配置表格属性的可能,对外汇/贵金属这种多变品种是高风险的僵化设计。
用继承把异类数据塞进同一个动态数组
想在一个动态数组里同时存 int、double 甚至自定义对象,难点在于数组单元格只能接受单一类型声明。标准库靠连接列表 + CObject 基类做这件事,但实测要对基类动大量手术,不如自己写一套轻量继承结构。 核心思路是:基类只留虚函数空壳,派生类各自覆盖。这样声明 CBase* 数组后,既能装 CA 也能装 CB,调用 set/get 时由虚表派发到真实类型。若不用继承而在基类把函数主体全写出来,二进制里每个对象都会背一份完整大函数代码;用继承后只占几个空函数,内存开销明显更低。 下面这段可直接贴进 MT5 跑。基类 CBase 构造函数打印自身,四个虚函数 set(int)/set(double)/get(int)/get(double) 都是空壳或返回 0;CA 私有 int temp,覆盖 set(int) 与 get(int);CB 私有 double temp,覆盖 set(double) 与 get(double)。OnStart 里 a 指向 CA、b 指向 CB,分别写入 15 和 13.3,打印出 a=15 b=13.3,最后 delete 释放。 别把虚函数当免费午餐 虚表派发省了内存,但每次调用多一次间接跳转;在高频 OnTick 里批量操作几万条异构数据时才谈得上划算,平时小数组用 union 或结构体更直白。外汇贵金属波动剧烈,任何数据结构回测结论都只是概率倾向,实盘前务必在策略测试器跑足样本。
class CBase { class="kw">public: CBase(){Print(__FUNCTION__);}; ~CBase(){Print(__FUNCTION__);}; class="kw">virtual class="type">void set(class="type">int sor){}; class="kw">virtual class="type">void set(class="type">class="kw">double sor){}; class="kw">virtual class="type">int get(class="type">int k){class="kw">return(class="num">0);}; class="kw">virtual class="type">class="kw">double get(class="type">class="kw">double k){class="kw">return(class="num">0);}; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class CA: class="kw">public CBase { class="kw">private: class="type">int temp; class="kw">public: CA(){Print(__FUNCTION__);}; ~CA(){Print(__FUNCTION__);}; class="type">void set(class="type">int sor){temp=sor;}; class="type">int get(class="type">int k){class="kw">return(temp);}; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class CB: class="kw">public CBase { class="kw">private: class="type">class="kw">double temp; class="kw">public: CB(){Print(__FUNCTION__);}; ~CB(){Print(__FUNCTION__);}; class="type">void set(class="type">class="kw">double sor){temp=sor;}; class="type">class="kw">double get(class="type">class="kw">double k){class="kw">return(temp);}; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { CBase *a; CBase *b; a=new CA(); b=new CB(); a.set(class="num">15); b.set(class="num">13.3); Print("a=",a.get(class="num">0)," b=",b.get(class="num">0.)); class="kw">delete a; class="kw">delete b; }
◍ 二维数组排序靠索引数组保结构
处理 MT5 里的数组时,我刻意避开标准类,主要卡点在引用方式:用中间索引数组做间接寻址,比直接用变量指向单元格要慢,因为数组单元格得先在内存里定位。但换来的是能按行整体搬动,不破坏一条记录里混装的多种类型数据。 一维数组排序是把乱序元素排顺;二维数组不能整体重排,只需按某一列排序,同时让所有行跟着换位置。行本身是绑定结构,所以排序时要另存一个初始索引数组,知道哪一行对应原单元格,就能整行调出来。 标准类的快速排序是不稳定算法,经典实现里不适合这种用法;即便改成稳定版(多一次数据复制+索引数组排序),速度仍可能优于冒泡,但递归吃栈。字符串数组更占堆栈,所以我用鸡尾酒排序(双向冒泡)兜底。下面这段演示按第 2 列排序后,索引数组 r 把原行 1、2、0 重排,数据结构原样不动。 外汇与贵金属行情跳变频繁,这类自定义排序多用于本地持仓/品种扫描面板,回测或实盘前请在 MT5 策略测试器里用真实 tick 验证栈占用。
before sorting by the class="num">2-nd column class="num">4 class="num">2 class="num">3 class="num">1 class="num">5 class="num">3 class="num">3 class="num">3 class="num">6 after sorting class="num">1 class="num">5 class="num">3 class="num">3 class="num">3 class="num">6 class="num">4 class="num">2 class="num">3 Initial array looks as following: a[class="num">0][class="num">0]= class="num">4; a[class="num">0][class="num">1]= class="num">2; a[class="num">0][class="num">2]= class="num">3; a[class="num">1][class="num">0]= class="num">1; a[class="num">1][class="num">1]= class="num">5; a[class="num">1][class="num">2]= class="num">3; a[class="num">2][class="num">0]= class="num">3; a[class="num">2][class="num">1]= class="num">3; a[class="num">2][class="num">2]= class="num">6; And the array of indexes of sorting by the class="num">2-nd column looks as: r[class="num">0]=class="num">1; r[class="num">1]=class="num">2; r[class="num">2]=class="num">0; Sorted values are returned according to the following scheme: a[r[class="num">0]][class="num">0]-> class="num">1; a[r[class="num">0]][class="num">1]-> class="num">5; a[r[class="num">0]][class="num">2]-> class="num">3; a[r[class="num">1]][class="num">0]-> class="num">3; a[r[class="num">1]][class="num">1]-> class="num">3; a[r[class="num">1]][class="num">2]-> class="num">6; a[r[class="num">2]][class="num">0]-> class="num">4; a[r[class="num">2]][class="num">1]-> class="num">2; a[r[class="num">2]][class="num">2]-> class="num">3;