从基础到中级:数组(四)(基础篇)
先分清数组的四个维度
MQL5 的数组不是只有一维。按官方文档与社区示例,数组可分为一维、二维、三维、四维共四种形态,其中一维和二维在 EA 与指标里最常用,三维及以上多见于多品种多周期矩阵计算。 一维数组本质是一条连续内存的序列,下标从 0 开始;二维数组可理解为「行×列」的表,适合存 OHLC 或多周期缓存。写错维度会在编译期直接报错,或在运行时越界导致 EA 异常终止。 下面这段声明展示了四种维度的写法,注意每多一维就多一对方括号。实际开发中四维数组极少手写,通常用动态结构替代。
class="type">class="kw">double a1[class="num">10]; class=class="str">"cmt">// 一维数组:class="num">10 个元素 class="type">class="kw">double a2[class="num">5][class="num">10]; class=class="str">"cmt">// 二维数组:class="num">5 行 class="num">10 列 class="type">class="kw">double a3[class="num">3][class="num">5][class="num">10]; class=class="str">"cmt">// 三维数组 class="type">class="kw">double a4[class="num">2][class="num">3][class="num">5][class="num">10]; class=class="str">"cmt">// 四维数组
「数组还没讲完,内存才是重头戏」
很多人看完前几篇数组文章,会觉得那些传参和初始化细节离实战很远。其实从这篇开始,才真正碰到了 MT5 里数组的核心用法——靠它把内存吃透。 先说一个硬前提:你必须搞懂上一节里「数组在声明作用域之外怎么处理」那段代码。没看懂就去翻,不然后面写的现实应用层逻辑会直接卡壳。 数组这个主题远没到收尾的时候。它覆盖面很广,我们会在后续小节里穿插着讲,顺带引入别的关联内容,避免一次性堆太多把人劝退。 接下来要做的第一件事,就是看数组怎么帮你更省、更可控地用内存。外汇和贵金属 EA 跑实盘时,内存分配不当可能引发掉线或重算延迟,这属于高风险环节,建议先在策略测试器里验证再上真仓。
◍ 用 sizeof 看清数组真实吃掉的字节
想理解程序怎么摆弄内存,先得甩掉「数组有几个元素就等于占几字节」的直觉。MQL5 里只有 char 和 uchar 是 1 字节类型,元素数和字节数才相等;short 占 2 字节,int 占 4 字节,double 占 8 字节,数组总字节永远是「元素数 × 单元素字节」。 下面这段静态数组对比很说明问题:同样 5 个元素 {2,3,3,5,8},char 版和 short 版元素数一模一样,但内存占用不同。 跑完代码后,ROM_1 输出「5 elements / 5 bytes」,ROM_2 输出「5 elements / 10 bytes」。差值就是 short 单元素 2 字节乘以 5 得出的,和 sizeof 运算符报的数完全对得上。 别把 sizeof 和 ArraySize 或 .Size() 混为一谈——后两者只数元素个数,sizeof 才告诉你真实内存块大小。后续做动态内存分配或释放时,靠 sizeof 算字节量是最直接的方式,现在先在 MT5 里把这段抄过去跑一遍,建立字节视角。
class="macro">#class="kw">property copyright "Daniel Jose" class="type">void OnStart(class="type">void) { const class="type">char ROM_1[] = {class="num">2, class="num">3, class="num">3, class="num">5, class="num">8}; const class="type">class="kw">short ROM_2[] = {class="num">2, class="num">3, class="num">3, class="num">5, class="num">8}; PrintFormat("ROM_1 contains %d elements and occupies %d bytes of memory", ROM_1.Size(), class="kw">sizeof(ROM_1)); PrintFormat("ROM_2 contains %d elements and occupies %d bytes of memory", ROM_2.Size(), class="kw">sizeof(ROM_2)); }
用枚举替掉看不懂的魔法数字
在 MT5 写 EA 或指标时,很多人习惯用 0、1、2 这样的十进制常量去切换格式或模式。上面这段头文件里的 ValueToString 函数就用了 4 个 case:case 0 走十进制,case 1 走三进制位,case 2 走十六进制,case 3 走单比特。调用方若直接写数字,读代码时根本不知道 15 行和 18 行传的 1 或 2 代表什么,得翻回头文件逐个比对。 这种写法不是不能跑,而是维护成本偏高。MQL5 的 enum 就是为解决这个问题而生的:它创建一种特殊数据类型,每加一个命名值就自动递增计数,代码里直接写有意义的名字。 把原来靠裸数字的头文件改成枚举后,调用处从 ValueToString(x, 1) 变成 ValueToString(x, eFormatOctLike) 之类,意图一眼可见。实测原调用代码无需改动仍能编译运行,只要枚举底层的序数值没动。等后面要扩格式,直接往枚举里加一项比满世界找魔法数字安全得多。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string ValueToString(class="type">ulong arg, class="type">char format) { const class="type">class="kw">string szChars = "0123456789ABCDEF"; class="type">class="kw">string sz0 = ""; while (arg) class="kw">switch (format) { case class="num">0: sz0 = StringFormat("%c%s", szChars[(class="type">uchar)(arg % class="num">10)], sz0); arg /= class="num">10; break; case class="num">1: sz0 = StringFormat("%c%s", szChars[(class="type">uchar)(arg & 0x7)], sz0); arg >>= class="num">3; break; case class="num">2: sz0 = StringFormat("%c%s", szChars[(class="type">uchar)(arg & 0xF)], sz0); arg >>= class="num">4; break; case class="num">3: sz0 = StringFormat("%c%s", szChars[(class="type">uchar)(arg & 0x1)], sz0); arg >>= class="num">1; break; class="kw">default: class="kw">return "Format not implemented."; } class="kw">return sz0; } class=class="str">"cmt">//+------------------------------------------------------------------+
「把十六进制数翻成四种进制看一眼」
在 MT5 里调试自定义指标时,经常要确认某个 ushort 或 ulong 变量在内存里的真实表达。上面第一段脚本直接把一个字面量 0xCADA 塞进 ushort,再调用 ValueToString 分别以十进制、八进制、十六进制、二进制打印出来。 0xCADA 换算成十进制是 51930,八进制是 0146652,二进制为 1100 1010 1101 1010。你在策略测试器日志里能看到这四行对照,用来核对位运算结果很直观。 第二段给出 ValueToString 的自实现版本:用一个枚举 eConvert 切换格式,核心是对 arg 不断取模或位与后查表 szChars("0123456789ABCDEF"),拼到字符串头部。比如 FORMAT_OCTAL 用 arg & 0x7 取低 3 位,FORMAT_DECIMAL 用 arg % 10,循环直到 arg 为 0。 把这两段存成 EA 丢进 MT5 跑一遍,就能验证自己的进制转换函数有没有写错,外汇和贵金属品种波动大、杠杆高,这类底层核对虽小但能少踩坑。
class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#include "Tutorial\File class="num">01.mqh" class="type">void OnStart(class="type">void) { class="type">class="kw">ushort value = 0xCADA; PrintFormat("Translation personal.\n" + "Decimal: %s\n" + "Octal : %s\n" + "Hex : %s\n" + "Binary : %s", ValueToString(value, class="num">0), ValueToString(value, class="num">1), ValueToString(value, class="num">2), ValueToString(value, class="num">3) ); } class="macro">#class="kw">property copyright "Daniel Jose" enum eConvert { FORMAT_DECIMAL, FORMAT_OCTAL, FORMAT_HEX, FORMAT_BINARY }; class="type">class="kw">string ValueToString(class="type">ulong arg, eConvert format) { const class="type">class="kw">string szChars = "0123456789ABCDEF"; class="type">class="kw">string sz0 = ""; while (arg) class="kw">switch (format) { case FORMAT_DECIMAL: sz0 = StringFormat("%c%s", szChars[(class="type">uchar)(arg % class="num">10)], sz0); arg /= class="num">10; break; case FORMAT_OCTAL: sz0 = StringFormat("%c%s", szChars[(class="type">uchar)(arg & 0x7)], sz0);