从基础到中级:数组(四)·进阶篇
(2/3)· 以为数组只是容器?sizeof与无限参数才是拉开业余与专业EA差距的分水岭
把 ushort 拆成四种进制字符串
这段 MQL5 代码演示了如何把一个 16 位无符号整数按十进制、八进制、十六进制、二进制四种格式转成字符串。核心在 ValueToString 函数里的 switch 分支:每种格式每轮只取最低几位,然后右移对应位数,循环拼接到 sz0 头部。 以 FORMAT_HEX 为例,第 28 行用 arg & 0xF 取低 4 位作为索引查 szChars 表,第 29 行 arg >>= 4 把数据右移 4 位,相当于每次消化一个十六进制字符。FORMAT_BINARY 则更细,只取 arg & 0x1 的 1 位后右移 1 位,因此 0xCADA 会展开成 16 个 0/1 字符。 调用端 OnStart 里写死了 ushort value = 0xCADA,也就是十进制 51930。在 MT5 里跑这段,专家日志会原样打印出 Decimal: 51930、Hex: CADA 以及对应的 Octal 与 Binary 串,你可以直接复制去验证自己写的进制转换有没有错位。 外汇与贵金属交易里这类底层数值处理多用于自定义协议或压缩存储,杠杆品种波动剧烈、风险高,动手改位移位数前先在策略测试器跑通再上实盘。
class="num">25. arg >>= class="num">3; class="num">26. break; class="num">27. case FORMAT_HEX: class="num">28. sz0 = StringFormat("%c%s", szChars[(class="type">uchar)(arg & 0xF)], sz0); class="num">29. arg >>= class="num">4; class="num">30. break; class="num">31. case FORMAT_BINARY: class="num">32. sz0 = StringFormat("%c%s", szChars[(class="type">uchar)(arg & 0x1)], sz0); class="num">33. arg >>= class="num">1; class="num">34. break; class="num">35. class="kw">default: class="num">36. class="kw">return "Format not implemented."; class="num">37. } class="num">38. class="num">39. class="kw">return sz0; class="num">40. } class="num">41. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">01. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">02. class="macro">#class="kw">property copyright "Daniel Jose" class="num">03. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">04. class="macro">#include "Tutorial\File class="num">01.mqh" class="num">05. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">06. class="type">void OnStart(class="type">void) class="num">07. { class="num">08. class="type">class="kw">ushort value = 0xCADA; class="num">09. class="num">10. PrintFormat("Translation personal.\n" + class="num">11. "Decimal: %s\n" + class="num">12. "Octal : %s\n" + class="num">13. "Hex : %s\n" + class="num">14. "Binary : %s", class="num">15. ValueToString(value, FORMAT_DECIMAL), class="num">16. ValueToString(value, FORMAT_OCTAL), class="num">17. ValueToString(value, FORMAT_HEX), class="num">18. ValueToString(value, FORMAT_BINARY) class="num">19. ); class="num">20. } class="num">21. class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 在 MQL5 里塞进不定数量的参数
C/C++ 用 va_start、va_arg、va_end 就能让函数吃进不定数量的参数,Java 也有类似机制,但 MQL5 原生没有这套。想在 MT5 里模拟,思路是把各种类型先拆成 uchar 字节流,再堆进一个动态数组,靠长度标记还原——相当于自己造一个变参通道。 示例里先声明了三个值:uint ui = 0xCADA0169、ushort us = 0x43BC、uchar uc = B'01011101'。随后定义 uchar Infos[] 作为承载容器,用 sizeof 算每个变量占用字节数,逐段 ArrayResize 扩数组并 memcpy 拷贝进去。 真正有趣的是还原端:调用方把整块 Infos 丢给一个过程,对方按之前写入的『每段字节数』标记,一段段反向拷回原类型变量。这样从 OnStart 发往外部过程的值,在接收侧能原样拿回 0xCADA0169、0x43BC、B'01011101'。 作者给出的紧凑版把重建放在循环里(外环第67行控制段数,内环第70行按字节数捞数据),输出和分步版一致但代码更短。外汇/贵金属自动化高风险,这套仅作机制演示,实盘前请在策略测试器跑通字节对齐逻辑。 别把 null 终止符当万能钥匙:二进制值里塞 0x00 会断流,用『中性长度头』标记每段大小比字符串终止符更稳,初学者容易在这里栽跟头。
<span class="number">class="num">01</span>. <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="number">class="num">02</span>. class="macro">#class="kw">property copyright <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>. class="macro">#include <span class="class="type">class="kw">string">"Tutorial\File class="num">01.mqh"</span> <span class="number">class="num">05</span>. <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="number">class="num">06</span>. <span class="keyword">class="type">void</span> OnStart(<span class="keyword">class="type">void</span>) <span class="number">class="num">07</span>. { <span class="number">class="num">08</span>. <span class="keyword">const</span> <span class="keyword">class="type">uint</span> ui = <span class="number">0xCADA0169</span>; <span class="number">class="num">09</span>. <span class="keyword">class="type">class="kw">ushort</span> us = <span class="number">0x43BC</span>; <span class="number">class="num">10</span>. class="type">uchar uc = B<span class="class="type">class="kw">string">&class="macro">#x27;class="num">01011101&class="macro">#x27;</span>; <span class="number">class="num">11</span>. <span class="number">class="num">12</span>. class="type">uchar Infos[]; <span class="number">class="num">13</span>. <span class="number">class="num">14</span>. PrintFormat(<span class="class="type">class="kw">string">"Translation personal.\n"</span> + <span class="number">class="num">15</span>. <span class="class="type">class="kw">string">"FUNCTION: [%s]\n"</span> + <span class="number">class="num">16</span>. <span class="class="type">class="kw">string">"ui => 0x%s\n"</span> + <span class="number">class="num">17</span>. <span class="class="type">class="kw">string">"us => 0x%s\n"</span> + <span class="number">class="num">18</span>. <span class="class="type">class="kw">string">"uc => B&class="macro">#x27;%s&class="macro">#x27;"</span>,
「用 ValueToString 把数值翻成可读串」
这段脚本演示了如何在 MT5 里把无符号整型、短整型和字符型变量,按十六进制或二进制格式直接打印出来。核心靠 ValueToString 的第二个参数控制进制:FORMAT_HEX 出十六进制,FORMAT_BINARY 出二进制。 具体看 OnStart 里的赋值:ui 是 0xCADA0169,us 是 0x43BC,uc 用字符字面量 B'01011101' 直接写二进制。PrintFormat 一次性把三者格式化进同一行日志,输出会是 ui => 0xCADA0169、us => 0x43BC、uc => B'01011101'。 后面那段动态数组操作值得注意:Infos 先用 ArrayResize 按 sizeof(ui)+1、sizeof(us)+1、sizeof(uc)+1 逐步扩容,再 ZeroMemory 清零。跑完 ArrayPrint 会显示该数组共占 8 字节(4+2+1,再加一个冗余字节来自首次扩容的 +1 逻辑)。外汇与贵金属交易使用此类工具属高风险,数值解析错误可能导致信号误判。 开 MT5 新建脚本粘贴下面代码,改个 uc 的二进制位,就能直观看到日志变化。
class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#include "Tutorial\File class="num">01.mqh" class="type">void OnStart(class="type">void) { const class="type">uint ui = 0xCADA0169; class="type">class="kw">ushort us = 0x43BC; class="type">uchar uc = B&class="macro">#x27;class="num">01011101&class="macro">#x27;; class="type">uchar Infos[]; PrintFormat("Translation personal.\n" + "FUNCTION: [%s]\n" + "ui => 0x%s\n" + "us => 0x%s\n" + "uc => B&class="macro">#x27;%s&class="macro">#x27;\n", __FUNCTION__, ValueToString(ui, FORMAT_HEX), ValueToString(us, FORMAT_HEX), ValueToString(uc, FORMAT_BINARY) ); ArrayResize(Infos, Infos.Size() + class="kw">sizeof(ui) + class="num">1); ArrayResize(Infos, Infos.Size() + class="kw">sizeof(us) + class="num">1); ArrayResize(Infos, Infos.Size() + class="kw">sizeof(uc) + class="num">1); ZeroMemory(Infos); Print("******************"); PrintFormat("The Infos block contains %d bytes.", Infos.Size()); ArrayPrint(Infos); Print("******************"); ArrayFree(Infos); }
把多类型变量塞进一个字节数组
在 MT5 脚本里经常需要把不同长度的变量打包成连续内存块,方便后续写文件或网络传输。下面这段逻辑演示了如何把 uint、ushort、uchar 各自的长度前缀和零填充区依次拼进同一个 uchar 数组 Infos。 初始时 ui=0xCADA0169(4字节)、us=0x43BC(2字节)、uc=B'01011101'(1字节)。每次打包都先按 sizeof(变量)+1 计算区块大小,用 ArrayResize 扩数组、ArrayFill 清零,再把变量自身长度写进首字节。 跑完三轮打包后,Infos 实际占用 4+1 + 2+1 + 1+1 = 10 字节。ArrayPrint 打出来你会看到前几个元素是 4,0,0,0,0, 2,0,0, 1,0 这样的排列——长度标记紧挨着对应的零填充区。 别在 resize 前漏算那 +1 很多人直接 sizeof(ui) 就扩数组,结果首字节的长度标记没地方写,counter 索引会越界报数组越界错误。先留 1 字节给长度,再填数据区,这个顺序在 MT5 里不能反。
class="type">void OnStart(class="type">void) { const class="type">uint ui = 0xCADA0169; class="type">class="kw">ushort us = 0x43BC; class="type">uchar uc = B&class="macro">#x27;class="num">01011101&class="macro">#x27;; class="type">uchar Infos[], counter = class="num">0; class="type">uint start, number; PrintFormat("Translation personal.\n" + "FUNCTION: [%s]\n" + "ui => 0x%s\n" + "us => 0x%s\n" + "uc => B&class="macro">#x27;%s&class="macro">#x27;\n", __FUNCTION__, ValueToString(ui, FORMAT_HEX), ValueToString(us, FORMAT_HEX), ValueToString(uc, FORMAT_BINARY) ); number = class="kw">sizeof(ui) + class="num">1; start = Infos.Size(); ArrayResize(Infos, start + number); ArrayFill(Infos, start, number, class="num">0); Infos[counter++] = class="kw">sizeof(ui); number = class="kw">sizeof(us) + class="num">1; start = Infos.Size(); ArrayResize(Infos, start + number); ArrayFill(Infos, start, number, class="num">0); Infos[counter++] = class="kw">sizeof(us); number = class="kw">sizeof(uc) + class="num">1; start = Infos.Size(); ArrayResize(Infos, start + number); ArrayFill(Infos, start, number, class="num">0); Infos[counter++] = class="kw">sizeof(uc); Print("******************"); PrintFormat("The Infos block contains %d bytes.", Infos.Size()); ArrayPrint(Infos); Print("******************"); ArrayFree(Infos); }
◍ 把多类型变量压进一个字节流
把 uint、ushort、uchar 三种类型依次塞进同一个 uchar 数组,是 MT5 里做私有协议封包的常见手法。下面这段逻辑先打印变量原值,再按「长度前缀 + 字节体」的格式写入 Infos 数组,最后用 ArrayPrint 把整块内存 dump 出来。 以 ui=0xCADA0169 为例,sizeof(ui) 为 4,写入时先放一个字节 4,再依次放 0xCA、0xDA、0x01、0x69 四个字节;us=0x43BC 占 2 字节,放 2 后接 0x43、0xBC;uc 是二进制字面量 B'01011101'(十进制 93),放 1 后接自身。最终 Infos 共 4+1 + 2+1 + 1+1 = 10 字节,终端里 ArrayPrint 会直接列出这 10 个 0~255 的值。 这种写法在外汇、贵金属 EA 里常用于把持仓标签、信号标记序列化后发往 DLL 或存进全局变量。需注意:跨端读写必须约定字节序,MT5 默认大端拆分,逆向解析时若用其他语言要手动对齐,否则可能读出乱码。杠杆品种波动剧烈,序列化错误会导致风控指令失效,实盘前务必在策略测试器跑一遍字节对齐。 小布盯盘里你可以直接把这段拷进脚本,把 ui 换成你的订单魔法码,看一眼 Infos 的十进制阵列是否和手算一致,就能确认封包逻辑没偏。
{
const class="type">uint ui = 0xCADA0169;
class="type">class="kw">ushort us = 0x43BC;
class="type">uchar uc = B&class="macro">#x27;class="num">01011101&class="macro">#x27;;
class="type">uchar Infos[];
counter = class="num">0;
class="type">uint start,
number;
PrintFormat("Translation personal.\n" +
"FUNCTION: [%s]\n" +
"ui => 0x%s\n" +
"us => 0x%s\n" +
"uc => B&class="macro">#x27;%s&class="macro">#x27;\n",
__FUNCTION__,
ValueToString(ui, FORMAT_HEX),
ValueToString(us, FORMAT_HEX),
ValueToString(uc, FORMAT_BINARY)
);
number = class="kw">sizeof(ui) + class="num">1;
start = Infos.Size();
ArrayResize(Infos, start + number);
Infos[counter++] = class="kw">sizeof(ui);
Infos[counter++] = (class="type">uchar)(ui >> class="num">24);
Infos[counter++] = (class="type">uchar)(ui >> class="num">16);
Infos[counter++] = (class="type">uchar)(ui >> class="num">8);
Infos[counter++] = (class="type">uchar)(ui & 0xFF);
number = class="kw">sizeof(us) + class="num">1;
start = Infos.Size();
ArrayResize(Infos, start + number);
Infos[counter++] = class="kw">sizeof(us);
Infos[counter++] = (class="type">uchar)(us >> class="num">8);
Infos[counter++] = (class="type">uchar)(us & 0xFF);
number = class="kw">sizeof(uc) + class="num">1;
start = Infos.Size();
ArrayResize(Infos, start + number);
Infos[counter++] = class="kw">sizeof(uc);
Infos[counter++] = (uc);
Print("******************");
PrintFormat("The Infos block contains %d bytes.", Infos.Size());
ArrayPrint(Infos);
Print("******************");
ArrayFree(Infos);
}