掌握 MQL5:从入门到精通(第四部分):关于数组、函数和全局终端变量(基础篇)
📘

掌握 MQL5:从入门到精通(第四部分):关于数组、函数和全局终端变量(基础篇)

第 1/3 篇

「数组、函数与全局终端变量先过一遍」

MQL5 里数组不是简单容器,它分静态、动态、多维三类,动态数组在运行时可用 ArrayResize 改变长度,这是写 EA 时缓存 K 线序列的常用手段。函数分自定义与内置,自定义函数必须声明返回类型,否则编译直接报错;全局终端变量则通过 GlobalVariableSet 写入终端内存,跨 EA 也能读取,适合做多图表协同的状态锁。 外汇与贵金属杠杆高、滑点跳空频繁,用全局变量做跨品种信号同步时,要意识到终端重启会清空未持久化的全局变量,概率上可能造成信号断层。 下面这段演示了动态数组扩容与全局变量写入的最小骨架,开 MT5 新建脚本粘进去能直接编译跑通:

MQL5 / C++
class="type">int arr[];
class="type">int init_flag=class="num">0;
class="type">void OnStart()
{
   ArrayResize(arr,class="num">10);
   for(class="type">int i=class="num">0;i<class="num">10;i++) arr[i]=i;
   GlobalVariableSet("my_flag",class="num">1);
   init_flag=(class="type">int)GlobalVariableGet("my_flag");
}

从变量到数组:MQL5 数据体系的三个跳板

前面的文章已经把 MQL5 里「数据怎么存」讲透了:数据要么进变量、要么进常量;MQL5 是强类型语言,每条数据都有类型,编译器靠类型分配内存并拦掉逻辑错误。类型分简单(基本)和复杂(用户定义)两种。要用这些数据,至少得声明一个函数,而任意代码块都能拆到独立文件里,再用 #include 预处理器拉进项目。 这一节往后要铺三个全局主题:数据数组,用来收尾程序内部的数据主体部分;全局终端变量,解决不同 MQL5 程序之间的简单数据交换;以及函数自身特性与它和变量之间的相互作用。 对外汇、贵金属交易者来说,MT5 里的强类型不是语法洁癖——类型搞错可能在回测里不报错,但实盘高频tick处理时直接内存异常,这类风险在杠杆品种上会被放大。

◍ 数组在 MQL5 里的真实用法

数组就是一堆同类型数据的容器,声明时写清类型和名字,再跟一对方括号,里面可以给定元素个数。MQL5 里价格历史、K线开盘时间、成交量这类序列数据,天然适合用数组装。 索引从 0 开始是铁律,最后一个元素的位置永远是 size - 1。比如一个长度为 2 的数组,能用的下标只有 0 和 1,写 myArray[2] 会越界报错。 声明时直接用花括号初始化也行,和结构体的写法一样,省得后面逐条赋值。下面这段在 MT5 里建个脚本跑一下,日志会打出 315 和 4.00,验证下标和初始化逻辑。

MQL5 / C++
<span class="keyword">class="type">int</span> myArray[<span class="number">class="num">2</span>]; <span class="comment">class=class="str">"cmt">// Describes an array of integers containing two elements</span>
<span class="comment">class=class="str">"cmt">// Fill the array with values:</span>
myArray[<span class="number">class="num">0</span>] = <span class="number">class="num">3</span>;
myArray[<span class="number">class="num">1</span>] = <span class="number">class="num">315</span>;
<span class="comment">class=class="str">"cmt">// Output the last element of this array to the log:</span>
<span class="functions">Print</span>(myArray[<span class="number">class="num">1</span>]); <span class="comment">class=class="str">"cmt">// class="num">315</span>
<span class="keyword">class="type">class="kw">double</span> anotherArray[<span class="number">class="num">2</span>] = {<span class="number">class="num">4.0</span>, <span class="number">class="num">5.2</span>}; <span class="comment">       class=class="str">"cmt">// A two-element array is initialized with two values</span>
<span class="functions">Print</span>( <span class="functions">DoubleToString</span>(anotherArray[<span class="number">class="num">0</span>],class="num">2) ); <span class="comment">class=class="str">"cmt">// Output class="num">4.00</span>

「嵌套方括号就是多维数组的骨架」

把数组当成容器,它里面还能套数组,这就成了多维数组。用书籍做类比最直观:字符排成行是第一维,行堆成段落是第二维,段落凑成一页是第三维。MQL5 里每多一层嵌套,就往左加一对方括号,外层容器括号始终在内侧容器的左边。 语言层面有两条硬限制:总维数不能超过 4,任意单一维度的最大元素数为 2147483647。初始化写法跟一维几乎一样,只是用花括号逐层列出子数组的元素。 下面这段代码把一维到三维的声明、赋值和读取都摊开了。char 类型的 paragraphArray[2][22] 表示 2 行 22 列;Print(CharToString(paragraphArray[1][3])) 会输出 'a',因为第二行(索引1)按序填了 'T','h','a'… 所以第 4 个字符是 a。末尾的 arrayToInitialize 用双层花括号直接初始化了 2×5 的整型矩阵。

MQL5 / C++
class="type">char stringArray[class="num">21];        class=class="str">"cmt">// One-dimensional array
class="type">char paragraphArray[class="num">2][class="num">22];  class=class="str">"cmt">// Two-dimensional array
class="type">char pageArray[class="num">3][class="num">2][class="num">22];    class=class="str">"cmt">// Three-dimensional array
class=class="str">"cmt">// Filling a two-dimensional array 
paragraphArray[class="num">0][class="num">0]=&class="macro">#x27;T&class="macro">#x27;;
paragraphArray[class="num">0][class="num">1]=&class="macro">#x27;h&class="macro">#x27;;
class=class="str">"cmt">// …
paragraphArray[class="num">1][class="num">20]=&class="macro">#x27;n&class="macro">#x27;;
paragraphArray[class="num">1][class="num">21]=&class="macro">#x27;.&class="macro">#x27;;
class=class="str">"cmt">// Access to an arbitrary element of a two-dimensional array
Print(CharToString(paragraphArray[class="num">1][class="num">3])); class=class="str">"cmt">// Will print "a" (why?)
class="type">int arrayToInitialize [class="num">2][class="num">5] = 
  {
    {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}
  }

动态数组的内存预留与尾插套路

MQL5 里处理历史记录、订单列表这类数量随时变的集合,静态数组不够用,得用动态数组:声明时方括号留空,初始长度就是 0,直接下标访问会直接崩程序。 开用之前必须调 ArrayResize 定大小。这个函数第一参是数组本身,第二参是新长度,第三参 reserve_size 可省略;若提前知道上限(比如最多 100 条),把第三参设成 100,实际长度仍由第二参决定(示例里是 1),但系统一次性多留 99 个元素的冗余内存。 为什么不直接每次加一个元素就 Resize 一次?因为每次向操作系统要内存,对 CPU 来说都是一笔不短的开销,要 1 个和要一批耗时差不多。所以一次预留一大块再慢慢填,比零敲碎打扩展快得多;当然内存有限,预留多少得在速度和占用间取平衡。 往尾端加数据是动态数组最高频的动作。ArraySize 返回当前元素数,配合 ArraySize+1 再 ArrayResize 就能追加。示例里 reserve 设 2,加第二个元素时只有写值那行变,其余完全重复,实际工程里应该抽成自定义函数。 多维动态数组只许第一维不写长度,例如 int a[][12] 合法,int b[][] 直接编译报错。真要双动态维,用结构包一个动态数组再搞结构数组,或者以后用类解决。 外汇与贵金属 EA 里这类数组操作出错会直接终止策略,复盘前建议在 MT5 策略测试器里跑一遍下方代码确认行为。

MQL5 / C++
class="type">int dinamicArray [];
ArrayResize(dinamicArray, class="num">1); class=class="str">"cmt">// The first parameter is the array and the second is the new size
ArrayResize(dinamicArray,class="num">1, class="num">100); class=class="str">"cmt">// The third parameter is the reserved(excess) size
dinamicArray[class="num">0] = class="num">3; class=class="str">"cmt">// Now our array contains exactly one element(see example class="num">7), its index is class="num">0
class="type">int size,          class=class="str">"cmt">// Number of elements in the array
    lastElemet;    class=class="str">"cmt">// Index of the last element
class="type">char stringArray[]; class=class="str">"cmt">// Our example dynamic array.
class=class="str">"cmt">//   Immediately description its size is class="num">0 (array cannot contain elements)
class=class="str">"cmt">// add an element to the end.

size = ArraySize(stringArray);     class=class="str">"cmt">// Find the current size of the array
size++;                            class=class="str">"cmt">// Array size should increase by class="num">1
ArrayResize(stringArray, size, class="num">2); class=class="str">"cmt">// Resize the array. In our example, the array will have no more than two elements.
lastElemet = size - class="num">1;             class=class="str">"cmt">// Numbering starts from class="num">0, so the number of the last element is class="num">1 less than the size of the array
stringArray[lastElement] = &class="macro">#x27;H&class="macro">#x27;;    class=class="str">"cmt">// Write the value
class=class="str">"cmt">// Now add one more element. The sequence of actions is absolutely the same.
size = ArraySize(stringArray);     class=class="str">"cmt">// Find the current size if the array
size++;                            class=class="str">"cmt">// Array size should increase by class="num">1
ArrayResize(stringArray, size, class="num">2); class=class="str">"cmt">// Resize the array. In our example, the array will have no more than two elements.
lastElemet = size - class="num">1;             class=class="str">"cmt">// Numbering starts from class="num">0, so the number of the last element is class="num">1 less than the size of the array
stringArray[lastElement] = &class="macro">#x27;i&class="macro">#x27;;    class=class="str">"cmt">// Write the value
class=class="str">"cmt">// Note that when adding the second element in this way, only on line changes: 
class=class="str">"cmt">//   the one that writes the actual value to a specific cell.
class=class="str">"cmt">//
class=class="str">"cmt">// This means that the solution can be written in a shorter form. For example, by creating a separate custom function for it.
class="type">int a [][class="num">12]; class=class="str">"cmt">// It&class="macro">#x27;s ok
class=class="str">"cmt">// class="type">int b [][]; // Compilation error: only the first index can be dynamic
class="kw">struct DinArray
    {
      class="type">int a       [];
    };
DinArray dinamicTwoDimensions [];        class=class="str">"cmt">// Dynamic array of structures
ArrayResize( dinamicTwoDimensions, class="num">1 );  class=class="str">"cmt">// Set size of the outer dimension
ArrayResize( dinamicTwoDimensions[class="num">0].a, class="num">1 ); class=class="str">"cmt">// Set size of the internal dimension

◍ 给动态二维数组首格赋值

在 MQL5 里用结构体数组套数组做动态二维存储时,第一维下标 0 对应首个结构体实例,其内部的 a[] 数组成员需用显式下标写入。 上面这行把 dinamicTwoDimensions[0].a[0] 设为 12,等价于在首行首列单元格写入一个整型数据,后续可借此做矩阵类的价格状态缓存。 外汇与贵金属市场波动剧烈、杠杆风险高,这类内存结构仅用于本地计算辅助,不代表任何方向判断。

MQL5 / C++
dinamicTwoDimensions[class="num">0].a[class="num">0] = class="num">12; class=class="str">"cmt">// Use cell to write data

常见问题

变量存单个值,数组存一串同类型值;当你要连续记录多根K线收盘价或批量计算结果时,直接用数组比开一堆变量清爽得多。
先用 ArrayResize 预留内存或靠动态数组自动扩容,再用 ArrayPush 一类尾插;不预留直接写越界下标必崩。
可以,小布能读你的代码片段并指出越界、未初始化、维度写错等基础问题,省去你逐行肉眼排查。
数嵌套方括号层数就是维度数,比如 arr[i][j] 是二维、arr[i][j][k] 是三维,把外层当行、内层当列去拆就行。
先确保第一维已扩容,再用 arr[0][0]=值 赋值;若维度未初始化就直接写会返回无效句柄或报错。