从基础到中级:数组(四)(基础篇)
📘

从基础到中级:数组(四)(基础篇)

第 1/3 篇

先分清数组的四个维度

MQL5 的数组不是只有一维。按官方文档与社区示例,数组可分为一维、二维、三维、四维共四种形态,其中一维和二维在 EA 与指标里最常用,三维及以上多见于多品种多周期矩阵计算。 一维数组本质是一条连续内存的序列,下标从 0 开始;二维数组可理解为「行×列」的表,适合存 OHLC 或多周期缓存。写错维度会在编译期直接报错,或在运行时越界导致 EA 异常终止。 下面这段声明展示了四种维度的写法,注意每多一维就多一对方括号。实际开发中四维数组极少手写,通常用动态结构替代。

MQL5 / C++
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 里把这段抄过去跑一遍,建立字节视角。

MQL5 / C++
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) 之类,意图一眼可见。实测原调用代码无需改动仍能编译运行,只要枚举底层的序数值没动。等后面要扩格式,直接往枚举里加一项比满世界找魔法数字安全得多。

MQL5 / C++
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 跑一遍,就能验证自己的进制转换函数有没有写错,外汇和贵金属品种波动大、杠杆高,这类底层核对虽小但能少踩坑。

MQL5 / C++
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);

常见问题

主要看元素类型、长度、维度数和内存布局;先确认是一维还是多维,再算每个元素占多少字节,避免越界和误读。
用 sizeof(数组名) 直接拿到总字节数,再除以单个元素大小就能算出元素个数,比肉眼数下标靠谱。
可以,把品种页打开让小布看你的逻辑,它能提示维度混淆、魔法数字和内存估算错误,你只管决策。
枚举让状态和下标变成有意义的名字,改逻辑时不易错,也方便别人一眼看懂数组在存什么。
用打印或换算函数输出十、二、八、十六进制对照,确认颜色码或位标志时不用心算,直接比对即可。