从基础到中级:联合(二)(基础篇)
📘

从基础到中级:联合(二)(基础篇)

第 1/3 篇

◍ 在 MT5 里看清两条线的纠缠

价格行为分析里,两条均线的「联合」状态比单线穿越更值得盯。所谓联合,是指快慢均线在窄幅区间内反复黏合、间距收敛到历史低分位,往往对应一段趋势后的动能真空期。 以 EURUSD 日线为例,当 20 日均线与 50 日均线差值绝对值跌破过去 60 根 K 线标准差的 0.3 倍时,后续 20 根内出现方向性突破的概率明显抬升,但突破方向本身不内置偏向。 打开 MT5 用以下脚本可把这种黏合程度直接画出来,参数里的 PeriodFast / PeriodSlow 可以随手改成你常用的组合,贵金属品种同样适用,但杠杆品种请先意识到回撤可能超过本金的高风险。

「联合与数组的承接关系」

本篇承接前作《从基础到中级:联合(一)》,继续拆解 MQL5 中 union(联合)的内存共用机制,并顺势扩展 array(数组)的相关认知。前一篇只铺了联合主题的入门部分,这个主题体量和数组一样大,两者在底层数据结构上紧密耦合。 如果你已经在 MT5 里写过结构体,会发现联合和结构体最大的差别是:同一时刻只有其中一个成员占有那块内存。理解这一点,是后面做行情数据压缩和指标缓冲复用的前提。 外汇与贵金属交易杠杆高、滑点随机,任何基于内存优化的 EA 都先在策略测试器用历史数据跑通再上实盘,回测不代表未来收益。

联合里塞数组,内存怎么被切分

联合在 MQL5 里不只是基础类型拼盘,你可以把自定义联合声明成数组,甚至数组里再套数组。代码 01 第 12 行写了 un_01 info[2];,表面看怪,因为很多人默认数组只能放 int、double 这类原始类型,实际上联合本身就是一种特殊数据类型,完全能当数组元素。 跑代码 01 时终端会打印 info 占 8 字节。这个大小由联合里最大的成员决定——u32_bits 是 uint(4 字节)吗?不对,这里联合由两个数组元素组成,每个 un_01 内部 u32_bits 占 4 字节、u8_bits[4] 也占 4 字节,所以单个 un_01 是 4 字节,info[2] 就是 8 字节。第 14 行 PrintFormat 出来的数直接可验证。 代码 02 把第 7 行改成 u8_bits[8],执行后输出和图 01 很像但值不同。若你把元素数改成 6(片段 01),跑出来图 03 直接报越界错误:因为 info 整体还是 8 字节,u8_bits 六个 uchar 占 6 字节,但 Swap 里用索引 7 访问第八个元素,踩到不属于自己的内存。改成 10 个元素(片段 02)则图 04 里部分值被 ZeroMemory 清零覆盖,看起来像“消失”,其实是被挪进内存其他区段。 代码 03 在第 52 行做了边界保护,复用 8 元素配置得到图 05 的干净镜像。把片段 01、02 套进去分别得图 06、07:不足填充或过填充都会让内存轮转结果扭曲分散。想自己复现,开 MT5 新建脚本粘下面代码,改第 7 行数字看终端字节变化就行。外汇贵金属编程测试请在策略测试器用模拟环境,实盘高危。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
union un_01
{
    class="type">uint   u32_bits;
    class="type">uchar  u8_bits[class="num">4];
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart(class="type">void)
{
    un_01 info[class="num">2];

    PrintFormat("The region is composed of %d bytes", class="kw">sizeof(info));
    ZeroMemory(info);
    View(info);
    Set(info, 0xA1B2C3D4E5F6789A);
    View(info);

◍ 用联合体在 MT5 里翻字节序

MQL5 的 union 能让同一段内存以不同宽度解读。下面这段定义了 un_01:u32_bits[2] 占 8 字节,u8_bits[8] 也正好映射到这 8 字节,二者在内存里重叠,改一个另一个跟着变。 Swap() 干的事是字节序反转——用 i 从 0 往中间走、j 从 sizeof(info)-1 往回走,把 u8_bits[i] 和 u8_bits[j] 互换,循环到 i<j 为止。对 8 字节数据跑一遍,大端小端就能互转。 Set() 把 64 位 ulong 拆进联合体:高 32 位右移 32 后塞进 arg[0].u32_bits,低 32 位用 0xFFFFFFFF 掩码塞进 arg[1].u32_bits。View() 则逐元素 PrintFormat 打出 Index 和 0x%I64X 十六进制值,方便在专家日志里核对。 实盘前先在 MT5 策略测试器跑 OnStart,故意喂一个 value=0x0123456789ABCDEF,看 View 输出是否字节反序为 EF CD AB 89 67 45 23 01。外汇与贵金属杠杆高,这类底层内存操作出错会静默崩脚本,验证通过再嵌进 EA。

MQL5 / C++
for (class="type">uchar c = class="num">0; c < info.Size(); c++)
   info[c] = Swap(info[c]);
   View(info);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Set(un_01 &arg[], class="type">class="kw">ulong value)
{
   arg[class="num">0].u32_bits = (class="type">uint)(value >> class="num">32);
   arg[class="num">1].u32_bits = (class="type">uint)(value & 0xFFFFFFFF);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void View(class="kw">const un_01 &arg[])
{
   Print("------------");
   for(class="type">uchar c = class="num">0; c < arg.Size(); c++)
      PrintFormat("Index: [ %d ] = 0x%I64X", c, arg[c].u32_bits);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
un_01 Swap(class="kw">const un_01 &arg)
{
   un_01 info = arg;

   for (class="type">uchar i = class="num">0, j = class="kw">sizeof(info) - class="num">1, tmp; i < j; i++, j--)
   {
      tmp = info.u8_bits[i];
      info.u8_bits[i] = info.u8_bits[j];
      info.u8_bits[j] = tmp;
   }

   class="kw">return info;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">uint info[class="num">2];
class="type">uchar info[class="num">2][class="num">4];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
union un_01
{
   class="type">uint  u32_bits[class="num">2];
   class="type">uchar u8_bits[class="num">8];
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart(class="type">void)

「共用体里翻字节:64位值如何被拆开再倒序」

在 MQL5 里用 union 可以把同一块内存按不同位宽解释。下面这段定义的 un_01,表面是 2 个 uint(共 8 字节),却又挂了 6 个 uchar 的视图,实际 sizeof(info) 打印出来是 8 字节,多余 2 字节的 uchar 视图只是重叠在尾部空闲区,验证时别被数组长度骗了。 OnStart 里先 ZeroMemory 清场,再塞一个 0xA1B2C3D4E5F6789A 进去。Set 函数把它拆成高 32 位和低 32 位:u32_bits[0] 拿到右移 32 位的部分,u32_bits[1] 用 0xFFFFFFFF 掩码取低段。View 逐索引打印,你会看到 index 0 是 A1B2C3D4、index 1 是 E5F6789A。 Swap 干的事很直白——把 u8_bits 从头尾向中间两两交换。原值按字节排是 A1 B2 C3 D4 E5 F6 78 9A(前 6 字节映射 u8_bits),跑完变成 9A 78 F6 E5 D4 C3,u32_bits 视角也就完全反转。外汇或贵金属 EA 里若涉及跨端字节序对齐,这种手法能直接复用,但金属杠杆高、滑点随机,实盘前务必在策略测试器用历史数据跑一遍确认无越界写。

MQL5 / C++
union un_01
{
   class="type">uint   u32_bits[class="num">2];
   class="type">uchar  u8_bits[class="num">6];
};

class="type">void OnStart(class="type">void)
{
   un_01 info;
   PrintFormat("The region is composed of %d bytes", class="kw">sizeof(info));
   ZeroMemory(info);
   View(info);
   Set(info, 0xA1B2C3D4E5F6789A);
   View(info);
   info = Swap(info);
   View(info);
}

class="type">void Set(un_01 &arg, class="type">class="kw">ulong value)
{
   arg.u32_bits[class="num">0] = (class="type">uint)(value >> class="num">32);
   arg.u32_bits[class="num">1] = (class="type">uint)(value & 0xFFFFFFFF);
}

class="type">void View(class="kw">const un_01 &arg)
{
   Print("------------");
   for(class="type">uchar c = class="num">0; c < arg.u32_bits.Size(); c++)
      PrintFormat("Index: [ %d ] = 0x%I64X", c, arg.u32_bits[c]);
}

un_01 Swap(class="kw">const un_01 &arg)
{
   un_01 info = arg;
   for (class="type">uchar i = class="num">0, j = class="kw">sizeof(info) - class="num">1, tmp; i < j; i++, j--)
   {
      tmp = info.u8_bits[i];
      info.u8_bits[i] = info.u8_bits[j];
      info.u8_bits[j] = tmp;
   }
   class="kw">return info;
}

用联合体把 64 位整数拆成字节流

MQL5 的 union 允许同一段内存以不同数据类型视图访问。下面这个 un_01 联合体在 32 位视图下是 2 个 uint(共 8 字节),在 8 位视图下则是 8 个 uchar,sizeof(info) 会输出 8 字节,两种视角共享物理存储。 OnStart 里先把联合体清零,再用 Set 把 0xA1B2C3D4E5F6789A 这个 64 位值拆进两个 uint:高 32 位通过右移 32 位得到,低 32 位用掩码 0xFFFFFFFF 截取。View 按 uint 打印索引,Debug 按 uchar 逐字节拼出内部内容,两者对照就能看清大小端布局。 第 19 行 info = Swap(info) 暗示存在字节序翻转函数(本段未给出实现),交换后再 View / Debug 一次,可验证每字节位置是否如预期镜像。外汇与贵金属 EA 里做协议封包或校验计算时,这种手法能直接省掉手工移位,但 MT5 联合体行为依赖编译器对齐,实盘前务必在策略测试器跑一遍确认字节序。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
union un_01
{
   class="type">uint   u32_bits[class="num">2];
   class="type">uchar  u8_bits[class="num">8];
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart(class="type">void)
{
   un_01 info;

   PrintFormat("The region is composed of %d bytes", class="kw">sizeof(info));
   ZeroMemory(info);
   Set(info, 0xA1B2C3D4E5F6789A);
   View(info);
   Debug(info);
   info = Swap(info);
   View(info);
   Debug(info);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Set(un_01 &arg, class="type">class="kw">ulong value)
{
   arg.u32_bits[class="num">0] = (class="type">uint)(value >> class="num">32);
   arg.u32_bits[class="num">1] = (class="type">uint)(value & 0xFFFFFFFF);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void View(class="kw">const un_01 &arg)
{
   Print("------------");
   for(class="type">uchar c = class="num">0; c < arg.u32_bits.Size(); c++)
      PrintFormat("Index: [ %d ] = 0x%I64X", c, arg.u32_bits[c]);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Debug(class="kw">const un_01 &arg)
{
   class="type">class="kw">string sz = "";

   Print("*************");
   for(class="type">uchar c = class="num">0; c < (class="type">uchar)arg.u8_bits.Size(); c++)
      sz = StringFormat("%s0x%X ", sz, arg.u8_bits[c]);
   PrintFormat("Number of elements in %cinfo.u8_bits.Size()%c is %d\nInternal content is [ %s ]", class="num">34, class="num">34, arg.u8_bits.Size(), sz);
   Print("*************");
}
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

用联合体(union)声明多个成员共享起始地址,赋值时只写其中一个成员,读另一个成员就能按不同视角解释同一段内存。
联合体大小按最大成员对齐,数组作为成员时整块数组共用基址,访问数组元素是在该共用区内按偏移切分,不改变共享本质。
小布可以读取你的策略代码并标出联合体成员赋值与读取不匹配的地方,提示可能的字节解释错误,帮你快速定位盘口数值异常。
因为多数平台是小端序,低位字节在前,拆成字节流后按大端传输或显示需手动倒序,否则高低位会反。
联合体直接按字节视图读取,少写移位和掩码,代码更短且不易错,但需注意平台字节序和对齐限制。