从基础到中级:数组(四)·综合运用
把多类型变量压进一个字节流
做跨模块通信时,经常要把 uint、ushort、uchar 不同宽度的变量塞进同一段 uchar 数组。下面这段脚本演示了如何带长度前缀地打包:先写该类型字节数,再按高位到低位拆字节。 常量初始化里 ui=0xCADA0169 占 4 字节,us=0x43BC 占 2 字节,uc=B'01011101' 是二进制字面量 0x5D。PrintFormat 用 ValueToString(…, FORMAT_HEX/FORMAT_BINARY) 把三种格式打印出来,方便对账。 打包逻辑是重复的:每次算 number=sizeof(var)+1,从 Infos.Size() 拿到起始位,ArrayResize 扩数组,第一字节存长度,后面依次右移取高字节。最终 Infos 共 5+3+2=10 字节,ArrayPrint 会原样吐出这 10 个 uchar 值,可直接拿去给另一段 Procedure 解析。 外汇与贵金属 EA 里这么干要留意:MT5 数组越界会直接崩脚本,Resize 前务必确认 counter 没跑偏;这类底层拼包在高杠杆品种上只是工具,不预示任何方向。
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[], 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("******************"); Procedure(Infos); }
◍ 字节流里按标记拆出多字节数值
这段 Procedure 函数演示了如何从一个 uchar 数组里按首字节标记还原不同长度的整数。arg[0] 是操作码:4 对应 uint(4 字节)、2 对应 ushort(2 字节)、1 对应 uchar(1 字节),后续字节按大端序拼回原值。 循环写法上,for(uchar c=0; c<arg.Size();) switch(arg[c++]) 先取标记再自增,内层再用 c++,i++ 双计数器搬字节。以 case 4 为例,value = (value << 8) | arg[c] 每轮左移 8 位再或上当前字节,循环条件卡死 i < sizeof(value),uint 下正好吃 4 个字节。 输出时分别用 ValueToString 配 FORMAT_HEX / FORMAT_BINARY,uint 和 ushort 打十六进制、uchar 打二进制串。外汇与贵金属 EA 里做私有协议解析时,这种手搓反序列化比 Struct 映射更可控,但字节序写错会静默读出脏数据,MT5 上跑前建议先喂一段已知数组验证。
class="type">void Procedure(const class="type">uchar &arg[]) { Print("Translation personal.\n" + "FUNCTION: ", __FUNCTION__); for (class="type">uchar c = class="num">0; c < arg.Size(); ) class="kw">switch(arg[c++]) { case class="num">4: { class="type">uint value = class="num">0; for (class="type">uchar i = class="num">0; (c < arg.Size()) && (i < (class="kw">sizeof(value))); c++, i++) value = (value << class="num">8) | arg[c]; Print("0x", ValueToString(value, FORMAT_HEX)); } break; case class="num">2: { class="type">class="kw">ushort value = class="num">0; for (class="type">uchar i = class="num">0; (c < arg.Size()) && (i < class="kw">sizeof(value)); c++, i++) value = (value << class="num">8) | arg[c]; Print("0x", ValueToString(value, FORMAT_HEX)); } break; case class="num">1: { class="type">uchar value = class="num">0; for (class="type">uchar i = class="num">0; (c < arg.Size()) && (i < class="kw">sizeof(value)); c++, i++) value = (value << class="num">8) | arg[c]; Print("B&class="macro">#x27;", ValueToString(value, FORMAT_BINARY), "&class="macro">#x27;"); } break; } }
「把多类型变量压进一个字节流」
在 MT5 脚本里做私有协议或自定义缓存时,常要把 uint、ushort、uchar 等不同宽度数据塞进同一个 uchar 数组。下面这段脚本演示了如何手工拆字节并顺序存放,最终 Infos 数组长度会是 10 字节(4+3+3)。 开头先定义常量 ui=0xCADA5169、us=0x43BC、uc=B'01011101',后者用二进制字面量直接写位模式,适合做标志位调试。PrintFormat 里用 ValueToString(..., FORMAT_HEX/FORMAT_BINARY) 能把变量按十六进制或二进制打印,开 MT5 跑一遍就能在专家日志看到原始值。 打包逻辑是:每写一种类型,先存 sizeof 占 1 字节,再按高位到低位右移强转 uchar 填入。uint 占 4 字节故写 5 个元素,ushort 写 3 个,uchar 写 2 个。ArrayResize 按当前 Size 追加,counter 自增保证不覆盖。 别把正态当圣经 这种手搓序列化在跨 EA 传参或写文件头时很实用,但字节序全靠自己移位是隐性坑;换平台跑之前先在脚本里 ArrayPrint 核对一遍,外汇和贵金属杠杆高,误读缓存可能引发错单。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "Tutorial\File class="num">01.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart(class="type">void) { const class="type">uint ui = 0xCADA5169; 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);
把字节流还原成可读数值的打印例程
上面这段 MQL5 代码展示了一个把压缩字节数组按「长度前缀 + 数据」格式解包并打印的例程,核心在 Procedure() 函数。它接收一个 uchar 类型引用数组 arg[],先输出当前函数名,再逐段读取。
外层 for 循环用 c 作游标,每次先取 arg[c++] 作为本段字节数 j,内层再把随后 j 个字节拼成 ulong。注意第 70 行 (value << 8) | arg[c] 是大端拼接:每读一个字节左移 8 位再或上新风数据,所以首字节在最高位。
第 72 行用 ValueToString 分别以十六进制和二进制打印,输出形如 0x1A3F B'0001101000111111'。如果你在 MT5 里跑这段,把 Infos 填成 {2,0xAB,0xCD,1,0x05},控制台会先打 AB 与 CD 拼成的 0xABCD,再打单字节 0x05,能直接验证拼接方向。
外汇与贵金属行情数据若用类似结构做本地缓存压缩,解包逻辑写错一端就可能把报价高低位颠倒,实盘前务必用已知字节序列在策略测试器里跑一遍。
class="num">53. Print("******************"); class="num">54. class="num">55. Procedure(Infos); class="num">56. class="num">57. ArrayFree(Infos); class="num">58. } class="num">59. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">60. class="type">void Procedure(const class="type">uchar &arg[]) class="num">61. { class="num">62. Print("Translation personal.\n" + class="num">63. "FUNCTION: ", __FUNCTION__); class="num">64. class="num">65. class="type">ulong value; class="num">66. class="num">67. for (class="type">uchar c = class="num">0; c < arg.Size(); ) class="num">68. { class="num">69. value = class="num">0; class="num">70. for (class="type">uchar j = arg[c++], i = class="num">0; (c < arg.Size()) && (i < j); i++, c++) class="num">71. value = (value << class="num">8) | arg[c]; class="num">72. Print("0x", ValueToString(value, FORMAT_HEX), " B&class="macro">#x27;", ValueToString(value, FORMAT_BINARY), "&class="macro">#x27;"); class="num">73. } class="num">74. }
◍ 把冗余代码压成清晰逻辑
这一节本来是承接前文、预告下一篇的过渡段,但技术落点很明确:前面示例里第 28 行到第 48 行那段实现,存在明显的冗余,目标是把它精简掉而不丢功能。 MQL5 其实已经内置了能替代这块手写逻辑的机制,作者留到后续文章展开。当下你能做的,是把附件 Anexo.zip(5.5 KB)里的示例真正跑一遍,定位那二十来行重复代码,思考如何用语言原生特性收拢。 外汇与贵金属波动剧烈、杠杆风险极高,这类代码重构只解决可读与维护成本,不暗示任何收益。先把当前示例练熟,等下一篇揭晓简化写法时,你才有对照的基准。