MQL5 编程基础:数组·进阶篇
🧮

MQL5 编程基础:数组·进阶篇

(2/3)· 搞懂静态动态边界与函数传参,EA 里的大规模数据才不会悄悄崩盘

含代码示例 第 2/3 篇
把一堆行情快照硬写成几十个独立变量,改起来像在迷宫里找线头。数组不过是带编号的同一类变量,括号键就在回车两边,先习惯它,后面省下的调试时间远超学习成本。

「MT5里多维数组的维度与内存真相」

MQL5 支持最多 4 维数组,但只有第一维能动态伸缩,其余维度在声明时必须写死。比如 double Variable[][3][3] 这种写法,后两维恒为 3,你只能用 ArrayResize() 去改第一维长度。 ArraySize() 返回的是元素总数而非第一维大小,这是个容易踩的坑。对一个 [][3][3] 调用 ArrayResize(Variable,3) 后,ArraySize() 拿到的是 27(3×3×3),若想拿第一维长度得自己除以 9,或者用 ArrayRange() 反推其他维乘积。 用值列表初始化多维数组时,每个子数组要用花括号隔开;三维就再套一层。代码里 {{1,2,3},{4,5,6},{7,8,9}} 这种结构直接映射成一个平面矩阵,读起来比线性的下标直观得多。 别把第一维长度硬编码进循环 函数参数没法传「任意维数」的多维数组,因为除第一维外维度必须明写在形参里,所以这类计算放在初始化阶段用宏或常量更干净。下面这段是原文核心语法的提炼:

MQL5 / C++
class="type">class="kw">double Variable[class="num">10][class="num">3];
class="type">class="kw">double Variable[class="num">10][class="num">10][class="num">10];
class="type">class="kw">double Variable[][class="num">3][class="num">3];
class="type">class="kw">double Variable[][class="num">3][class="num">3];
ArrayResize(Variable,class="num">3);
class="type">int Size = ArraySize(Variable);
class="type">class="kw">double Variable[][class="num">3][class="num">3];
ArrayResize(Variable,class="num">3);
class="type">int Size=ArraySize(Variable)/class="num">9; class=class="str">"cmt">// Determine the size of the first dimension
for(class="type">int i=class="num">0; i<Size; i++) {
  for(class="type">int j=class="num">0; j<class="num">3; j++) {
    for(class="type">int k=class="num">0; k<class="num">3; k++) {
      class=class="str">"cmt">//  some manipulations on the Var[i][j][k] element;
    }  
  }  
}
class="type">int Elements=ArrayRange(Variable,class="num">1)*ArrayRange(Variable,class="num">2);
class="type">int Size=ArraySize(Variable)/Elements;
class="type">int Elements=class="num">1; class=class="str">"cmt">// One element for a one-dimensional array
class="type">int n=class="num">1; class=class="str">"cmt">// Start with the second dimension(dimensions are numbered from zero)
class="kw">while(ArrayRange(Variable,n) > class="num">0){ class=class="str">"cmt">// Until there are elements in the dimension
  Elements*=ArrayRange(Variable,n); class=class="str">"cmt">// Multiplication of the number of elements
  n++; class=class="str">"cmt">// Increase in the dimension&class="macro">#x27;s number
}
class="macro">#define SIZE1 class="num">3
class="macro">#define SIZE2 class="num">3
class="macro">#define TOTAL SIZE1*SIZE2 
class="type">class="kw">double Variable[class="num">3][class="num">3];
class="type">class="kw">double Variable[][class="num">3]={{class="num">1, class="num">2, class="num">3},{ class="num">4, class="num">5, class="num">6},{class="num">7, class="num">8, class="num">9}};
class="type">class="kw">double Variable[][class="num">3][class="num">3]={
{
{class="num">1, class="num">2, class="num">3},
{class="num">4, class="num">5, class="num">6},
{class="num">7, class="num">8, class="num">9}
},
{
{class="num">10, class="num">20, class="num">30},
{class="num">40, class="num">50, class="num">60},
{class="num">70, class="num">80, class="num">90}
},
{
{class="num">100, class="num">200, class="num">300},
{class="num">400, class="num">500, class="num">600},
{class="num">700, class="num">800, class="num">900}
}
};
ArrayInitialize(Variable,class="num">1);
class="type">class="kw">double var[class="num">3][class="num">3][class="num">3];
ArrayFill(Variable,class="num">0,class="num">9,class="num">1);

◍ 数组批量填值的偏移陷阱

MQL5 里 ArrayFill 的第二个参数是起始偏移,不是目标索引的硬位置。上面两行若作用在长度为 27 的一维数组 Variable 上,第一次从偏移 9 开始填 9 个元素,值全是 10;第二次从偏移 18 开始再填 9 个,值全是 100。 容易踩坑的是:起始偏移 + 填充数量若超过数组上限会直接报数组越界错误。比如数组只有 20 个元素,第二行偏移 18 填 9 个就写到 26,必然 runtime 报错。 在 MT5 里开个脚本验证最快:声明 double Variable[27],跑这两行后打印 index 8/9/17/18/26,能看到 9 号起变 10、18 号起变 100,中间 0–8 仍是初始 0。外汇与贵金属 EA 里用这种填充做缓冲初始化时,记得先确认数组实际 ASIZE,高风险品种下数组越界会让策略直接停摆。

MQL5 / C++
ArrayFill(Variable,class="num">9,class="num">9,class="num">10);
ArrayFill(Variable,class="num">18,class="num">9,class="num">100);

数组进函数只会走引用通道

MQL5 里变量默认按值进函数,Func 拿到的是副本;数组相反,只能按引用传,函数内动的就是原数组本身。 看这段:int x=1 按值丢给 Func(int arg),arg 改成 2,退出后 x 还是 1。换成 int &arg 传引用,同样的赋值让外部 x 变成 2——这就是引用和值的差别。 数组声明时必须带括号和 &,例如 void Func(double &arg[]) 才合法;多维数组除第一维外维度大小要写死,double &arg[][3][3] 这种写法在编译期就锁了后两维。 若数组只读取不修改,用 const 限定更稳,能挡掉误写还让编译器做优化。 外汇与贵金属 EA 开发中,数组引用误改原始行情缓冲是常见隐蔽 bug,高频策略尤需警惕这类高风险写法。

MQL5 / C++
class="type">int x=class="num">1;
Func(x);
class="type">void  Func(class="type">int arg){
   arg=class="num">2;
}
class="type">int x=class="num">1;
Func(x);
class="type">void  Func(class="type">int &arg){
   arg=class="num">2;
}
class="type">void Func(class="type">class="kw">double &arg[]){
   class=class="str">"cmt">// ...
}
class="type">class="kw">double var[][class="num">3][class="num">3];
class="type">void Func(class="type">class="kw">double &arg[][class="num">3][class="num">3]){
   class=class="str">"cmt">// ...
}
class="macro">#define SIZE1 class="num">3
class="macro">#define SIZE2 class="num">3
class="type">class="kw">double Var[][SIZE1][SIZE2];
class="type">void Func(class="type">class="kw">double &arg[][SIZE1][SIZE2]){
   class=class="str">"cmt">// ...
}

「数组存取时第一维与总量的坑」

把数组写进文件再读回来,最容易被忽略的是:一维数组里 ArraySize() 返回的元素总数恰好等于第一维长度,但多维数组里这俩完全不是一回事。只存总量不存维度信息,载入时就会按默认形状硬塞,形状错位还不报错。 下面这套函数能直接抄进 MT5 用。SaveArrayToFile 先写元素总数再写整个数组,LoadArrayFromFile 读回总数后 ArrayResize 到该值再读数据,仅对一维 double 数组通用。 三维数组的读取要自己算第一维。LoadArrayFromFile3 里用 SIZE1*SIZE2 当每“层”元素数,用读到的 SizeTotal 除以它得到第一维长度再 Resize。若文件实际是 2*3 而你当 3*3 读,SizeTotal 对不上就会静默截断或越界。 注意 Var[2][3] 和 Var[3][2] 总量都是 6,只存总数分辨不出。要彻底通用,写文件时依次存:元素总数、维度数、各维大小、数组本体;MT5 里除第一维外其他维度不能在运行时改大小,所以针对固定形状写专用函数反而更省事。若以后想外部控维度,上更大数组加变量或走 OOP 封装。

MQL5 / C++
class="type">bool SaveArrayToFile(class="type">class="kw">string FileName,class="type">class="kw">string &Array[])
  {
class=class="str">"cmt">//--- Open the file
   class="type">int h=FileOpen(FileName,FILE_TXT|FILE_WRITE);
   if(h==-class="num">1) class="kw">return(false); class=class="str">"cmt">// Error opening the file
class=class="str">"cmt">//--- Write to the file
   FileWriteInteger(h,ArraySize(Array),INT_VALUE); class=class="str">"cmt">// Write the array size
   FileWriteArray(h,Array); class=class="str">"cmt">// Write the array
class=class="str">"cmt">//--- Close the file
   FileClose(h);
   class="kw">return(true); class=class="str">"cmt">// Saving complete
   }
class="type">bool LoadArrayFromFile(class="type">class="kw">string FileName,class="type">class="kw">double &Array[])
  {
class=class="str">"cmt">//--- Open the file
   class="type">int h=FileOpen(FileName,FILE_BIN|FILE_READ);
   if(h==-class="num">1) class="kw">return(false); class=class="str">"cmt">// Error opening the file
class=class="str">"cmt">//--- Read the file
   class="type">int Size=FileReadInteger(h,INT_VALUE); class=class="str">"cmt">// Read the number of array elements
   ArrayResize(Array,Size); class=class="str">"cmt">// Resize the array. 
                                    class=class="str">"cmt">// In one-dimensional arrays the size of the first dimension is equal to the number of array elements.
   FileReadArray(h,Array); class=class="str">"cmt">// Read the array from the file
class=class="str">"cmt">//--- Close the file
   FileClose(h);
   class="kw">return(true); class=class="str">"cmt">// Reading complete
   }
class="type">bool LoadArrayFromFile3(class="type">class="kw">string FileName,class="type">class="kw">double &Array[][SIZE1][SIZE2])
  {
class=class="str">"cmt">//--- Open the file
   class="type">int h=FileOpen(FileName,FILE_BIN|FILE_READ);
   if(h==-class="num">1)class="kw">return(false); class=class="str">"cmt">// Error opening the file
class=class="str">"cmt">//--- Read the file   
   class="type">int SizeTotal=FileReadInteger(h,INT_VALUE); class=class="str">"cmt">// Read the number of array elements
   class="type">int Elements=SIZE1*SIZE2; class=class="str">"cmt">// Calculate the number of elements 
   class="type">int Size=SizeTotal/Elements; class=class="str">"cmt">// Calculate the size of the first dimension
   ArrayResize(Array,Size); class=class="str">"cmt">// Resize the array
   FileReadArray(h,Array); class=class="str">"cmt">// Read the array
class=class="str">"cmt">//--- Close the file
   FileClose(h);
   class="kw">return(true); class=class="str">"cmt">// Reading complete
   }

◍ 动态数组的区块扩容比逐个加 1 快得多

写 EA 或脚本时,如果挂单数、信号数这类数据量运行前未知,动态数组是绕不开的。最偷懒的做法是每次新增一条就 ArrayResize 加 1 个元素,逻辑没错,但实测 5 万次追加在 MT5 里能跑出数秒延迟,对实时盯盘来说太拖。 更合理的办法是按区块扩:先估一个 ChunkSize(类里默认 1024),不够了一次性加一整块。随附测试里同样 5 万次写入,区块方式几乎瞬时完成,差距在一个数量级以上。 下面这个类把扩容逻辑封好了,Element[] 放 public 段,急用可直接下标访问;AddValue 里先 m_Size++ 再判断越界,越界才按块加 m_ReservedSize。文件放 MQL5\Include 下的 CDynamicArray.mqh 就能 #include 用。 别把逐个扩容当无害写法 小布盯盘跑高频订单扫描时,这种隐性卡顿会让 Tick 处理掉帧,外汇和贵金属波动快、杠杆高,延迟可能直接放大滑点风险,建议直接抄区块版。

MQL5 / C++
class CDynamicArray
  {
class="kw">private:
   class="type">int                m_ChunkSize;      class=class="str">"cmt">// Chunk size
   class="type">int                m_ReservedSize; class=class="str">"cmt">// Actual size of the array
   class="type">int                m_Size;          class=class="str">"cmt">// Number of active elements in the array
class="kw">public:
   class="type">class="kw">double             Element[];       class=class="str">"cmt">// The array proper. It is located in the class="kw">public section, 
                                       class=class="str">"cmt">// so that we can use it directly, if necessary
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//|  Constructor                                                      |
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class="type">void CDynamicArray(class="type">int ChunkSize=class="num">1024)
     {
      m_Size=class="num">0;                         class=class="str">"cmt">// Number of active elements
      m_ChunkSize=ChunkSize;            class=class="str">"cmt">// Chunk size
      m_ReservedSize=ChunkSize;         class=class="str">"cmt">// Actual size of the array
      ArrayResize(Element,m_ReservedSize); class=class="str">"cmt">// Prepare the array
     }
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//|  Function for adding an element at the end of array               |
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class="type">void AddValue(class="type">class="kw">double Value)
     {
      m_Size++; class=class="str">"cmt">// Increase the number of active elements
      if(m_Size>m_ReservedSize)
        { class=class="str">"cmt">// The required number is bigger than the actual array size
         m_ReservedSize+=m_ChunkSize; class=class="str">"cmt">// Calculate the new array size
         ArrayResize(Element,m_ReservedSize); class=class="str">"cmt">// Increase the actual array size
        }
      Element[m_Size-class="num">1]=Value; class=class="str">"cmt">// Add the value
     }
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//|  Function for getting the number of active elements in the array  |
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class="type">int Size()
     {
      class="kw">return(m_Size);
     }
  };
class="type">int n=class="num">50000;
   class="type">class="kw">double ar[];
   CDynamicArray da;
class=class="str">"cmt">//--- Option class="num">1 (increasing the size by the 1st element)
   class="type">long st=GetTickCount(); class=class="str">"cmt">// Store the start time 
   ArrayResize(ar,class="num">0); class=class="str">"cmt">// Set the array size to zero 

逐元素扩容与分块追加的耗时对照

在 MT5 里往动态数组塞大量数据,两种写法对执行时间的影响很直接。下面这段实测逻辑把 n 个元素分别用 ArrayResize 逐次扩容、和用自定义分块容器 da.AddValue 追加,各跑一遍并用 GetTickCount 打点。 Alert 弹出的『Option 1』和『Option 2』毫秒数,就是两种方案在同一台终端上的真实开销差。若 n 取到 10 万级,逐元素 ArrayResize 的重复拷贝会让 Option 1 的耗时明显偏高,而分块策略倾向把扩容次数压到对数级。 外汇与贵金属行情高频刷新,这类底层数组操作若写进 EA 的 OnTick,耗时差可能放大滑点概率;建议直接开 MT5 把 n 改成不同量级跑一下,看自己机器上的毫秒差值再决定用哪种。

MQL5 / C++
  for(class="type">int i=class="num">0;i<n;i++)
    {
      ArrayResize(ar,i+class="num">1); class=class="str">"cmt">// Resize the array sequentially
      ar[i]=i;
    }
  Alert("Option class="num">1: "+IntegerToString(GetTickCount()-st)+" ms"); class=class="str">"cmt">// Message regarding the amount of time required to perform the first option
class=class="str">"cmt">//--- Option class="num">2 (increasing the size class="kw">using chunks)
  st=GetTickCount(); class=class="str">"cmt">// Store the start time 
  for(class="type">int i=class="num">0;i<n;i++)
    {
      da.AddValue(i); class=class="str">"cmt">// Add an element
    }
  Alert("Option class="num">2: "+IntegerToString(GetTickCount()-st)+" ms"); class=class="str">"cmt">// Message regarding the amount of time required to perform the second option
  }
交给小布盯盘看盘口
这些数组维度的诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到数组越界与冗余分配的提示,你专注策略逻辑就好。

常见问题

是,所有维度均从 0 起算,最后一维索引比该维长度小 1,遍历时注意上限用 ArrayRange 取而非硬编码。
频繁重分配有开销,倾向在初始化阶段预估容量或按块扩容,避免在每根 tick 里无节制缩容扩容。
MQL5 数组默认引用传递,函数内改动会反映到原数组;若需隔离应显式拷贝到临时数组再处理。
可以,小布盯盘的 AIGC 会对常见数组用法做静态提示,比如静态大小写死、动态未 Resize 就访问等,外汇贵金属波动大高风险,这类低级错最容易在极端行情触发。
二进制 FileWriteArray 更快且保精度,CSV 便于人工排查但转换有损耗,实盘倾向二进制做高频快照。