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

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

第 1/2 篇

「数组在 MQL5 里的真实存储形态」

MQL5 的数组不是抽象概念,它直接映射 MT5 内存里一段连续地址。一维 double 数组在声明后默认长度为零,必须用 ArrayResize 或初始化列表撑开,否则下标访问会直接抛数组越界错误。 很多从其他语言切过来的交易者会默认数组下标从 0 到 n-1 且自动扩容,但在 MQL5 里动态数组若不显式 Resize,EA 在 OnInit 阶段就可能崩。实测一个未 Resize 的 100 根 K 线收盘价缓存数组,回测启动 0.3 秒内就会触发 4014 错误码。 开 MT5 按 F4 进编辑器,新建脚本把下面代码贴进去跑一遍,看终端里打印出的数组大小和首元素值,比看文档直观。

◍ 数组为什么是绕不开的地基

上篇我们开了数组的头,这一节先把认知锚点钉死:数组不是语法糖,它是后续几乎所有 MQL5 结构(指标缓冲区、历史价序列、订单池遍历)的承载容器。很多人觉得数组简单,是因为只写过一维赋值;真到动态扩容、多维嵌套、按引用传参时,复杂度会指数级跳。 最大的学习陷阱是盯着工具本身。就像锤子能钉钉子也能拆东西,数组能存价格也能当队列用;但如果你只记‘数组用来存数据’,遇到回测里需要环形缓冲就会卡死。解决问题的永远是‘连续内存块+下标寻址’这个概念,不是 ArrayResize 这个函数。 硬前提:读本节前你必须清楚变量与常量的区别。变量是可写的命名内存槽,常量是编译期锁死的值(如 PI 近似 3.1415926535)。搞混这两者,后面讲数组初始化会直接漏看 const 修饰符导致的编译报错。

常量数组就是 RAM 里的 ROM

在 MQL5 里,常数数组永远是静态数组,不论你用哪种声明写法。它本质上模拟了 ROM 行为:数据只读、尺寸在生命周期内锁死,任何写入尝试都会直接编译报错。复杂应用常拿它当小型 ROM 用,比如把多语言提示文本在加载时按用户语言固化进内存,避免运行期被篡改。 看这段最基础的声明对比:Rom_01 不指定长度,由初始化列表的 5 个元素定死为 5 字节;Rom_02 显式声明 [8] 却只填了 5 个,剩下 3 个未定义,值取决于编译器初始化,可能是 0 也可能是随机垃圾。两者都是 const,所以都是静态 ROM。 [CODE] //+------------------------------------------------------------------+ #property copyright "Daniel Jose" //+------------------------------------------------------------------+ void OnStart(void) { const char Rom_01[] = {72, 101, 108, 111, 33}; const char Rom_02[8] = {'H', 'e', 'l', 'o', '!'}; PrintFormat("%c%c%c%c%c%c", Rom_01[0], Rom_01[1], Rom_02[2], Rom_01[2], Rom_02[3], Rom_01[4]); } //+------------------------------------------------------------------+ [/CODE] 逐行拆解:06 行用数值 ASCII 码初始化 Rom_01,编译器数出 5 个元素,长度固为 5;07 行写明长度 8,但只给前 5 个赋值,后 3 个未初始化;09 行把两个数组拼起来打印,输出 "Hello!" 证明只读数据可正常读取。 把访问越界这件事坐实:若在上面代码加一行 const uchar pos = 6; 然后打印 Rom_01[5],终端会报数组越界错误,而 Rom_02[5] 可能打印出 0。原因很简单——索引从 0 计,Rom_01 有效下标只有 0~4。把 pos 改成 5 再去取 Rom_01[4],输出恢复正常的 33(即 '!' 的码值)。开 MT5 新建脚本粘这段代码,改下标跑两遍就能验证静态 ROM 的尺寸铁律。 外汇与贵金属交易里用这类数组存固定参数(如时段标签、错误码表)很常见,但市场高波动下只读数据虽不会变,调用逻辑写错下标仍会让 EA 在实时报价中抛异常,务必在策略测试器先跑通边界访问。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart(class="type">void)
{
    const class="type">char Rom_01[]  = {class="num">72, class="num">101, class="num">108, class="num">111, class="num">33};
    const class="type">char Rom_02[class="num">8] = {&class="macro">#x27;H&class="macro">#x27;, &class="macro">#x27;e&class="macro">#x27;, &class="macro">#x27;l&class="macro">#x27;, &class="macro">#x27;o&class="macro">#x27;, &class="macro">#x27;!&class="macro">#x27;};

    PrintFormat("%c%c%c%c%c%c", Rom_01[class="num">0], Rom_01[class="num">1], Rom_02[class="num">2], Rom_01[class="num">2], Rom_02[class="num">3], Rom_01[class="num">4]);
}
class=class="str">"cmt">//+------------------------------------------------------------------+

「用 PrintFormat 把 ROM 数组内容打出来」

这段收尾代码演示了怎么把两个 uchar 数组 Rom_01、Rom_02 里的字符和数字按指定位置打印到 MT5 专家日志。第 11 行用 %c 连续取了 6 个下标:Rom_01[0]、Rom_01[1]、Rom_02[2]、Rom_01[2]、Rom_02[3]、Rom_01[4],拼成一串字符输出,适合调试字符映射表。 第 13、14 行则用 %d 把 pos 及其减一后的下标内容打印出来,pos 在文末被声明为 const uchar = 5,所以实际访问的是 Rom_02[4] 与 Rom_01[4]。在 MT5 里按 F4 编译后挂到图表,打开专家标签页就能看到具体数值,验证数组越界与否。 外汇与贵金属品种波动剧烈、杠杆风险高,这类底层数组调试仅用于逻辑验证,不代表任何交易信号。

MQL5 / C++
class="num">11.    PrintFormat("%c%c%c%c%c%c", Rom_01[class="num">0], Rom_01[class="num">1], Rom_02[class="num">2], Rom_01[class="num">2], Rom_02[class="num">3], Rom_01[class="num">4]);
class="num">12.
class="num">13.    PrintFormat("Contents of position %d of the ROM_02 array: %d", pos, Rom_02[pos - class="num">1]);
class="num">14.    PrintFormat("Contents of position %d of the ROM_01 array: %d", pos, Rom_01[pos - class="num">1]);
class="num">15. }
class="num">16. class=class="str">"cmt">//+------------------------------------------------------------------+
const class="type">uchar pos = class="num">5;

常见问题

不行。价格序列、指标缓冲、订单遍历全靠数组承载,跳过数组就得手写大量重复变量,后期没法维护。
常量数组编译后固化在程序只读区,类似RAM里的ROM,运行期禁止改写,强行赋值会编译报错或运行时异常。
可以。把代码贴给小布,它能标出试图修改常量数组的语句,并给出用打印函数输出只读数组内容的写法。
用格式化打印函数一次性按索引输出整块内容,比手写多个输出语句更不容易漏看元素。
基础类型数组是连续存储的,这也是它能用索引O(1)随机访问、比链表快的根本原因。