掌握 MQL5:从入门到精通(第三部分)复杂数据类型和包含文件(基础篇)
📘

掌握 MQL5:从入门到精通(第三部分)复杂数据类型和包含文件(基础篇)

第 1/2 篇

◍ MQL5 里的复杂类型与 include 机制

MQL5 不止提供 int、double 这类标量,真正写策略时躲不开复杂数据类型:string、数组、struct、class 与动态对象。它们让你能把行情、订单、指标缓冲区组织成可复用的结构,而不是满屏散变量。 包含文件(.mqh)用 #include 引入,相当于把公共函数、常量、类定义抽出来共享。一个 EA 拆成主文件 + 多个 mqh,编译后仍合为单一可执行体,但工程可读性明显提升。 官方示例文档(2025-03-07 发布)在社区获得 1180 次查看、7 条讨论,说明复杂类型与 include 是实盘脚本绕不过的基建。开 MT5 按 F4 建个 .mqh 写个 struct,再在主文件 #include 它,比复制粘贴函数更不容易出 bug。

「从简单类型搭出可维护的复杂结构」

上一篇已经把 double、enum、string 这类简单数据类型,以及变量和常量的概念讲清了。但实盘写 EA 时,你迟早会遇到一种情况:把行情、持仓、参数绑在一起当成一个整体来处理,比散着用一堆独立变量顺手得多。本文要落地的,就是结构(struct)、联合(union)和初阶类(class)这几种“自己拼”的复杂类型。 MT5 编译器有个死规矩:用数据前必须先声明类型,因为它得提前算好要占多少内存。所以不管是简单类型还是后面要写的 struct,本质都是给编译器一张“内存排座表”。 代码能不能长期维护,关键看模块化程度。团队开发不用说,单人写策略也一样——两个月后回来加功能,在几百行平铺逻辑里找 bug,远不如在分好文件、拆好函数的片段里改来得快。把相关逻辑塞进独立数据结构、用 #include 拆文件,改起来心理负担小得多。

把零散变量收进一个结构里

做日内策略时,一笔交易的开仓小时、分钟、秒数往往要一起用。若拆成三个独立 int 变量,代码散、易错;MQL5 的 struct 能把它们打包成 IntradayTime 这种自定义类型,后面所有该类型变量都自带 hours、minutes、seconds 字段,用点号访问即可。 结构里的字段不限于基础类型,也能塞 string 甚至别的结构。下面这段代码先定义了带 stopLoss、takeProfit、magicNumber 的 TradeParameters,再把它作为 TradeSignal 的一个字段,相当于给信号结构体嵌套了参数块。 struct IntradayTime { int hours; int minutes; int seconds; string timeCodeString; }; // 注意花括号后的分号 IntradayTime dealEnterTime; dealEnterTime.hours = 8; dealEnterTime.minutes = 15; dealEnterTime.timeCodeString = "GMT+2"; // Nested structure struct TradeParameters { double stopLoss; double takeProfit; int magicNumber; }; // Main structure struct TradeSignal { string symbol; // Symbol name ENUM_ORDER_TYPE orderType; // Order type (BUY or SELL) double volume; // Order volume TradeParameters params; // Nested structure as parameter type }; // Using the structure void OnStart() { // Variable description for the structure TradeSignal signal; // Initializing structure fields signal.symbol = Symbol(); signal.orderType = ORDER_TYPE_BUY; signal.volume = 0.1; signal.params.stopLoss = 20; signal.params.takeProfit = 40; signal.params.magicNumber = 12345; // Using data in an expression Print("Symbol: ", signal.symbol); Print("Order type: ", signal.orderType); Print("Volume: ", signal.volume); Print("Stop Loss: ", signal.params.stopLoss); Print("Take Profit: ", signal.params.takeProfit); Print("Magic Number: ", signal.params.magicNumber); } TradeSignal signal = { "EURUSD", ORDER_TYPE_BUY,

  • 1,

{20.0, 40.0, 12345} }; 用常量初始化时可省去逐字段赋值,花括号内按声明顺序写值就行;只写前几个,其余自动清零。MetaEditor 里输入变量名加小数点,会弹出字段列表,比翻文档快。 MQL5 自带 MqlDateTime、MqlTick 等预定义结构,字段用法同理。外汇与贵金属杠杆高、滑点突兀,结构体虽好,也只是把风险数据归置清楚,不替你过滤行情噪声。

MQL5 / C++
class="kw">struct IntradayTime {
  class="type">int hours;
  class="type">int minutes;
  class="type">int seconds;
  class="type">class="kw">string timeCodeString;
};  class=class="str">"cmt">// note the semicolon after the curly brace
IntradayTime dealEnterTime;
dealEnterTime.hours = class="num">8;
dealEnterTime.minutes = class="num">15;
dealEnterTime.timeCodeString = "GMT+class="num">2";
class=class="str">"cmt">// Nested structure
class="kw">struct TradeParameters
{
   class="type">class="kw">double stopLoss;
   class="type">class="kw">double takeProfit;
   class="type">int magicNumber;
};
class=class="str">"cmt">// Main structure
class="kw">struct TradeSignal
{
   class="type">class="kw">string          symbol;    class=class="str">"cmt">// Symbol name
   ENUM_ORDER_TYPE orderType; class=class="str">"cmt">// Order type(BUY or SELL)
   class="type">class="kw">double          volume;    class=class="str">"cmt">// Order volume
   TradeParameters params;    class=class="str">"cmt">// Nested structure as parameter type
};
class=class="str">"cmt">// Using the structure
class="type">void OnStart()
{
class=class="str">"cmt">// Variable description for the structure
   TradeSignal signal;
class=class="str">"cmt">// Initializing structure fields
   signal.symbol = Symbol();
   signal.orderType = ORDER_TYPE_BUY;
   signal.volume = class="num">0.1;
   signal.params.stopLoss = class="num">20;
   signal.params.takeProfit = class="num">40;
   signal.params.magicNumber = class="num">12345;
class=class="str">"cmt">// Using data in an expression
   Print("Symbol: ",  signal.symbol);
   Print("Order type: ",  signal.orderType);
   Print("Volume: ",  signal.volume);
   Print("Stop Loss: ",  signal.params.stopLoss);
   Print("Take Profit: ",  signal.params.takeProfit);
   Print("Magic Number: ",  signal.params.magicNumber);
}
TradeSignal signal = 
  {
   "EURUSD", 
   ORDER_TYPE_BUY, 
   class="num">0.1, 

     {class="num">20.0,  class="num">40.0,  class="num">12345}
  };

◍ 结构对齐与联合体绕过类型墙

MQL5 里的 struct 默认是打包存储,字段一个挨一个排。想控制占用字节数,要么手动补占位成员,要么用 pack() 操作符显式对齐——括号里只收 1、2、4、8、16 这几个数,对应字段对齐到多少字节边界。 排成员有个经验:大块数据放前面(比如 long 先于 int),能少踩很多坑。想知道某个字段离结构体开头偏移多少字节,用 offsetof 就行。拿前面的 TradeParameters 试,offsetof(TradeParameters, stopLoss) 实际打出 0,说明该字段正卡在头部。 [CODE] struct pack(sizeof(long)) MyStruct1 { // structure members will be aligned on an 8-byte boundary }; // or struct MyStruct2 pack(sizeof(long)) { // structure members will be aligned on an 8-byte boundary }; Print (offsetof(TradeParameters, stopLoss)); // Result: 0 //--- enum ENUM_LEAVES { rounded, oblong, pinnate }; //--- struct Tree { int trunks; ENUM_LEAVES leaves; }; //--- struct Bush { int trunks; ENUM_LEAVES leaves; }; //--- union Plant { Bush bush; Tree tree; }; //--- void OnStart() { Tree tree = {1, rounded}; Bush bush; Plant plant; // bush = tree; // Error! // bush = (Bush) tree; // Error! plant.tree = tree; bush = plant.bush; // No problem... Print(EnumToString(bush.leaves)); } //+------------------------------------------------------------------+ // Creating a type union AnyNumber { long integerSigned; // Any valid data types (see further) ulong integerUnsigned; double doubleValue; }; // Using AnyNumber myVariable; myVariable.integerSigned = -345; Print(myVariable.integerUnsigned); Print(myVariable.doubleValue); [/CODE] 代码逐行拆:pack(sizeof(long)) 即按 8 字节对齐;offsetof 那行直接输出 0 验证偏移。Tree 和 Bush 字段完全相同,但 bush = tree 会报错,因为不是同型或直继关系。借 Plant 联合体先塞 tree 再取 bush,绕过了赋值限制。AnyNumber 联合体演示同一存储单元被解释为 long / ulong / double,赋值 -345 后打印另两个视图,能看到二进制 reinterpret 的结果。 不含字符串、类、指针、动态数组的结构叫简单结构,能直接丢给外部 DLL 函数,也能在同型或直继间互拷;其余情况只能逐字段搬。若真要把 Bush 当 Tree 用,联合体是最快路径,但数字字段互转才稳。 联合体的禁区得记牢:动态数组、字符串、对象/函数指针、类、带构造析构的结构,以及嵌套了上述任一类型的结构,全不能进 union。结构里沾了 string,编译期直接红。外汇与贵金属 EA 调 DLL 本就高风险,结构体布局错一个字节,传参就可能让仓位计算偏掉。

MQL5 / C++
class="kw">struct pack(class="kw">sizeof(class="type">long)) MyStruct1
     {
      class=class="str">"cmt">// structure members will be aligned on an class="num">8-byte boundary
     };
class=class="str">"cmt">// or
class="kw">struct MyStruct2 pack(class="kw">sizeof(class="type">long))
     {
      class=class="str">"cmt">// structure members will be aligned on an class="num">8-byte boundary
     };
Print(offsetof(TradeParameters, stopLoss)); class=class="str">"cmt">// Result: class="num">0
class=class="str">"cmt">//---
enum ENUM_LEAVES
  {
   rounded,
   oblong,
   pinnate
  };
class=class="str">"cmt">//---
class="kw">struct Tree
  {
   class="type">int               trunks;
   ENUM_LEAVES       leaves;
  };
class=class="str">"cmt">//---
class="kw">struct Bush
  {
   class="type">int               trunks;
   ENUM_LEAVES       leaves;
  };
class=class="str">"cmt">//---
union Plant
  {
   Bush bush;
   Tree tree;
  };
class=class="str">"cmt">//---
class="type">void OnStart()
  {
   Tree tree = {class="num">1, rounded};
   Bush bush;
   Plant plant;
class=class="str">"cmt">// bush = tree; // Error!
class=class="str">"cmt">// bush = (Bush) tree; // Error!
   plant.tree = tree;
   bush = plant.bush; class=class="str">"cmt">// No problem...
   Print(EnumToString(bush.leaves));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// Creating a type
union AnyNumber {
  class="type">long   integerSigned;  class=class="str">"cmt">// Any valid data types(see further)
  class="type">ulong  integerUnsigned;
  class="type">class="kw">double doubleValue;
};
class=class="str">"cmt">// Using
AnyNumber myVariable;
myVariable.integerSigned = -class="num">345;
Print(myVariable.integerUnsigned);
Print(myVariable.doubleValue);

常见问题

用struct把同组变量包成一个类型,比如定义order_info存symbol、lots、sl、tp,调用时直接传结构体,少写错变量名。
编译器按最大成员做字节对齐会插padding;用编译器的sizeof看实际大小,字段按大小降序排可减小空洞。
小布能扫你贴的代码,标出结构体重定义、字段冗余和对齐浪费,并给合并建议,省你手动翻文件。
同一块内存想按不同数据类型解释时用union,如价格 tick 既看 double 又看 uchar 数组,省内存且绕类型墙,但别同时写读不同类型。
在包含文件头部用#ifndef式宏或#pragma once防重入,公共结构只在一处定义,其他文件直接#include即可。