从基础到中级:数组(二)·进阶篇
◍ RAM 数组的动静之分与内存脏数据陷阱
把声明里的 const 去掉,数组就从只读的 ROM 类型变成了 RAM 类型。元素数量可以固定也可以不固定,内容在程序跑起来之后随便改——这是和常量数组最本质的区别。 声明时带初始化列表的动态数组(如 char Ram_01[] = {72,101,108,111,33})实际会被编译器当成静态处理,因为元素个数在编译期就定了;而 char Ram_02[8] 这种显式给长度的也是静态,但预留了比初始化元素更多的空间。 真正完全动态的数组是连长度都不写的那种。这时候内存由程序员自己管:用 ArrayResize 或 new 分配,用完必须显式释放,否则 MT5 不会自动回收。 动态分配的内存内容是完全随机的。下面代码第 9 行分配空间后,第 11–15 行只写了部分位置,第 22 行读出来的值就是操作系统留下的「垃圾」——可能是别的程序残留,也可能是 0,每次运行都不一定相同。 别假设刚分配的内存里是干净值。在 MQL5 里用 ArrayInitialize 或标准库调用把整块区域刷一遍,比事后调 Bug 省事得多。外汇和贵金属 EA 开发中,这类未初始化读取在实盘可能触发难以复现的异常,属高风险编码习惯。
<span class="number">class="num">01</span>. <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="number">class="num">02</span>. <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">copyright</span> <span class="class="type">class="kw">string">"Daniel Jose"</span> <span class="number">class="num">03</span>. <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="number">class="num">04</span>. <span class="keyword">class="type">void</span> <span class="functions">OnStart</span>(<span class="keyword">class="type">void</span>) <span class="number">class="num">05</span>. { <span class="number">class="num">06</span>. <span class="keyword">class="type">char</span> Ram_01[] = {<span class="number">class="num">72</span>, <span class="number">class="num">101</span>, <span class="number">class="num">108</span>, <span class="number">class="num">111</span>, <span class="number">class="num">33</span>}; <span class="number">class="num">07</span>. <span class="keyword">class="type">char</span> Ram_02[<span class="number">class="num">8</span>] = {<span class="class="type">class="kw">string">&class="macro">#x27;H&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;e&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;l&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;o&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;!&class="macro">#x27;</span>}; <span class="number">class="num">08</span>. <span class="number">class="num">09</span>. <span class="keyword">const</span> <span class="keyword">class="type">uchar</span> pos = <span class="number">class="num">5</span>; <span class="number">class="num">10</span>. <span class="number">class="num">11</span>. <span class="functions">PrintFormat</span>(<span class="class="type">class="kw">string">"%c%c%c%c%c%c"</span>, Ram_01[<span class="number">class="num">0</span>], Ram_01[<span class="number">class="num">1</span>], Ram_02[<span class="number">class="num">2</span>], Ram_01[<span class="number">class="num">2</span>], Ram_02[<span class="number">class="num">3</span>], Ram_01[<span class="number">class="num">4</span>]); <span class="number">class="num">12</span>. <span class="number">class="num">13</span>. <span class="functions">PrintFormat</span>(<span class="class="type">class="kw">string">"Contents of position %d of the RAM_02 array: %d"</span>, pos, Ram_02[pos - <span class="number">class="num">1</span>]); <span class="number">class="num">14</span>. <span class="functions">PrintFormat</span>(<span class="class="type">class="kw">string">"Contents of position %d of the RAM_01 array: %d"</span>, pos, Ram_01[pos - <span class="number">class="num">1</span>]); <span class="number">class="num">15</span>. } <span class="number">class="num">16</span>. <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
字符数组的两种初始化与越界改写实测
MQL5 里 char 数组既能用 ASCII 十进制数填充,也能直接用字符字面量。第一段脚本里 Ram_01 用 {72,101,108,111,33} 存了 Hello! 的码值,Ram_02 用 {'H','e','l','o','!'} 只写了 5 个元素却声明了 [8] 长度,剩下 3 个位置默认补 0。 PrintFormat 那行故意交叉取两个数组的下标:Ram_01[0]=72('H')、Ram_01[1]=101('e')、Ram_02[2]=108('l')、Ram_01[2]=108('l')、Ram_02[3]=111('o')、Ram_01[4]=33('!'),终端会打出 Hello!。注意 Ram_02 第 4 位(下标 3)原本是 'o' 而非 'l',交叉拼串时别直觉填错。 pos 设为 5,Ram_02[pos-1] 即下标 4,原值是 0(未初始化位)。脚本把它改成 '$'(码值 36)后再次打印,Ram_02 该位从 0 变成 36;Ram_01 同位置原本就是 33('!'),改写后也变成 36。这验证了 char 数组允许运行时按下标改值,且越过的声明长度内未赋值区可被安全写入。 第二段脚本用 r[10] 存 {72,101,108,108,111,33},只填了前 6 个,下标 6、7、8、9 为 0。for 循环跑 ArraySize(r)=10 次,把每个码值格式化成三位十进制逗号串打印,你会看到末尾跟着 000, 000, 000, 000。 把 r[7] 改成 36 后重跑循环,输出里第 8 个数从 000 变成 036,说明未初始化槽位在声明长度内同样可写。外汇与贵金属 EA 里用这类数组缓存价格字节流时,务必确认下标不超出声明长度,否则 MT5 可能静默出错。
class="macro">#class="kw">property copyright "Daniel Jose" class="type">void OnStart(class="type">void) { class="type">char Ram_01[] = {class="num">72, class="num">101, class="num">108, class="num">111, class="num">33}; class="type">char Ram_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;}; class="type">uchar pos = class="num">5; PrintFormat("%c%c%c%c%c%c", Ram_01[class="num">0], Ram_01[class="num">1], Ram_02[class="num">2], Ram_01[class="num">2], Ram_02[class="num">3], Ram_01[class="num">4]); PrintFormat("Contents of position %d of the Ram_02 array: %d", pos, Ram_02[pos - class="num">1]); PrintFormat("Contents of position %d of the Ram_01 array: %d", pos, Ram_01[pos - class="num">1]); Ram_02[pos - class="num">1] = &class="macro">#x27;$&class="macro">#x27;; PrintFormat("Contents of position %d of the Ram_02 array: %d", pos, Ram_02[pos - class="num">1]); Ram_01[pos - class="num">1] = &class="macro">#x27;$&class="macro">#x27;; PrintFormat("Contents of position %d of the Ram_01 array: %d", pos, Ram_01[pos - class="num">1]); } class="macro">#class="kw">property copyright "Daniel Jose" class="type">void OnStart(class="type">void) { class="type">char r[class="num">10] = {class="num">72, class="num">101, class="num">108, class="num">108, class="num">111, class="num">33}; class="type">class="kw">string sz0; sz0 = ""; for (class="type">uchar c = class="num">0; c < ArraySize(r); c++) sz0 = StringFormat("%s%03d, ", sz0, r[c]); Print(sz0); Print("Modifying a value..."); r[class="num">7] = class="num">36; sz0 = ""; for (class="type">uchar c = class="num">0; c < ArraySize(r); c++) sz0 = StringFormat("%s%03d, ", sz0, r[c]); Print(sz0); }
「字符数组的内存布局与动态扩容」
MQL5 里 char 数组本质是连续字节,直接塞 ASCII 码就能拼出可读字符串。下面这段脚本把 72/101/108/111/33 依次写入 Ram_01[0..4],用 PrintFormat 的 %c 打印出来会得到「Hello!」——注意第 15 行故意写了两次 Ram_01[2],所以输出是「Helo!」而非「Hello!」,这种手误在实盘日志里很难一眼看出。 数组越界访问不会编译报错但会崩脚本。Ram_01 只声明了 5 个元素,pos 设为 5,第 17 行读 Ram_01[pos-1] 即 Ram_01[4] 是合法的(值 33),第 18 行把 Ram_01[4] 改成 '$'(ASCII 36)后再次打印,值从 33 变成 36,说明末尾字符被静默替换。 更稳妥的做法是用 ArrayResize 动态开内存。第二段代码里 ArrayResize(r,10) 返回 10,代表数组现在能装 10 个 char;前 5 位同样填 Hello! 的码值,后面 5 位默认是 0。用 for 循环把每个元素按 %03d 格式拼成字符串,日志会显示「072, 101, 108, 111, 033, 000, 000, 000, 000, 000, 」——第 24 行改 r[7]=36 后重跑循环,第 8 个数字从 000 变成 036。外汇与贵金属脚本跑这套逻辑时波动大、 tick 密集,数组越界可能引发 EA 异常终止,属高风险操作,建议本地先测。
class="macro">#class="kw">property copyright "Daniel Jose" class="type">void OnStart(class="type">void) { class="type">char Ram_01[class="num">5], pos = class="num">5; Ram_01[class="num">0] = class="num">72; Ram_01[class="num">1] = class="num">101; Ram_01[class="num">2] = class="num">108; Ram_01[class="num">3] = class="num">111; Ram_01[class="num">4] = class="num">33; PrintFormat("%c%c%c%c%c%c", Ram_01[class="num">0], Ram_01[class="num">1], Ram_01[class="num">2], Ram_01[class="num">2], Ram_01[class="num">3], Ram_01[class="num">4]); PrintFormat("Contents of position %d of the Ram_01 array: %d", pos, Ram_01[pos - class="num">1]); Ram_01[pos - class="num">1] = &class="macro">#x27;$&class="macro">#x27;; PrintFormat("Contents of position %d of the Ram_01 array: %d", pos, Ram_01[pos - class="num">1]); } class="macro">#class="kw">property copyright "Daniel Jose" class="type">void OnStart(class="type">void) { class="type">char r[]; class="type">class="kw">string sz0; PrintFormat("Allocated enough position for %d elements", ArrayResize(r, class="num">10)); r[class="num">0] = class="num">72; r[class="num">1] = class="num">101; r[class="num">2] = class="num">108; r[class="num">3] = class="num">111; r[class="num">4] = class="num">33; sz0 = ""; for (class="type">uchar c = class="num">0; c < ArraySize(r); c++) sz0 = StringFormat("%s%03d, ", sz0, r[c]); Print(sz0); Print("Modifying a value..."); r[class="num">7] = class="num">36; sz0 = ""; for (class="type">uchar c = class="num">0; c < ArraySize(r); c++)
◍ 用 char 数组把数字拼成可读串
这段脚本演示了在 MT5 里先给 char 数组留 10 个位置,再手动填几个 ASCII 码,最后用循环拼成逗号分隔的三位字符串打印出来。ArrayResize(r,10) 的返回值直接喂给 PrintFormat,日志会先吐出 "Allocated enough position for 10 elements",说明实际分配了 10 个元素空间。 填进去的 72,101,108,111,33 对应 H、e、l、o、! 的 ASCII 值,第一轮循环输出 "072, 101, 108, 111, 033, 000, 000, 000, 000, 000, "——没赋值的槽位靠 ZeroMemory 清零,所以补的是 000。 中途改了 r[7]=36(即 '$' 符号的码值),第二轮打印变成 "...000, 036, 000, 000, ",能看到第 8 个位置从 000 翻成 036。外汇与贵金属行情波动剧烈、杠杆风险高,这类数组操作只建议在策略调试或日志追踪里用,别拿去硬扛实盘信号。 收尾调用 ArrayFree(r) 释放内存,避免脚本反复跑的时候漏内存。想验证的话,直接把下面代码贴进 MT5 脚本文件跑一遍,改 r[7] 的值就能观察打印差异。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart(class="type">void) { class="type">char r[]; class="type">class="kw">string sz0; PrintFormat("Allocated enough position for %d elements", ArrayResize(r, class="num">10)); ZeroMemory(r); r[class="num">0] = class="num">72; r[class="num">1] = class="num">101; r[class="num">2] = class="num">108; r[class="num">3] = class="num">111; r[class="num">4] = class="num">33; sz0 = ""; for (class="type">uchar c = class="num">0; c < ArraySize(r); c++) sz0 = StringFormat("%s%03d, ", sz0, r[c]); Print(sz0); Print("Modifying a value..."); r[class="num">7] = class="num">36; sz0 = ""; for (class="type">uchar c = class="num">0; c < ArraySize(r); c++) sz0 = StringFormat("%s%03d, ", sz0, r[c]); Print(sz0); ArrayFree(r); } class=class="str">"cmt">//+------------------------------------------------------------------+
把这条线请下神坛
RAM 里造出 ROM 行为、动态与静态数组的取舍,这些手法只是数组处理里的一条技术线,不是什么高不可攀的终极方案。附带 ZIP 里 3.32 KB 的代码就是文中那些例子的实体,直接拖进 MT5 的 MetaEditor 就能编译跑通,改两行下标看看返回是不是预期的 0 或垃圾值。 未初始化元素藏垃圾这件事,没人能靠假设绕过去,外汇和贵金属杠杆高、滑点狠,一段读错内存的逻辑可能让 EA 在实盘里乱开仓。把这套代码当练习沙盒,比背结论有用。 线用完了就收,下次碰到数组越界或交互异常,回来翻这两段逻辑足够。