MQL5数组编程从怕到精通
全面拆解静态动态多维数组与性能陷阱
数组到底是什么:和变量有什么关系
很多编程新手对数组心存惧意,其实数组和普通的变量类似。不考虑符号特性的细节,语句的编写没有太大区别,无论是使用简单变量还是数组。区别只在于使用数组时变量名称包含括号,且在声明时只需写一次名字并在括号中指定元素数量。在处理大量实际任务时,数组相比多个独立变量具有压倒性优势:你可以用循环统一处理成百上千个元素,而不必复制粘贴代码。
数组是具有相同名称的编号变量集,基本属性包括名称、类型(int、double等)和大小。MQL5中数组元素索引从0开始,因此最后一个元素的索引比元素数量小1。例如 double Variable[3] 包含 Variable[0]、Variable[1]、Variable[2]。这种从0开始计数虽初看不便,但相比从1开始的语言,在指针运算和内存布局上更一致,也便于与C++生态对接。
double Variable[3]; Variable[0]=1; Variable[1]=2; Variable[2]=Variable[0]+Variable[1];
静态数组与动态数组的核心区别
数组分为静态和动态。静态数组在声明时指定大小,之后无法在程序中更改;动态数组声明时不指定大小,使用 ArrayResize() 在运行时多次调整。静态大小可直接写数字或用 #define 常量,动态则必须先用 ArrayResize 设定尺寸才能使用。
#define SIZE 3 double Variable[SIZE]; double Variable[]; ArrayResize(Variable,3);
释放数组使用 ArrayFree(),其将大小设为0,等价于 ArrayResize(Variable,0),在不再需要数组或函数开头重置收集数据时很有用。ArrayIsDynamic() 可判断数组是否动态,返回 true/false。动态数组是MQL5处理不定长数据(如挂单列表)的基础,但错误使用会带来严重性能问题。
ArrayFree(Variable); bool dynamicArray=ArrayIsDynamic(Variable);
数组初始化与批量赋值技巧
声明时可用值列表直接初始化,如 string Variable[]={"Button 1","Button 2","Button 3"},此时虽未指定大小仍是静态数组。建议用 ArraySize() 而非硬编码数字获取大小,方便后续改列表不改主代码。若需同值填充用 ArrayInitialize(),部分填充用 ArrayFill()。
string Variable[] = {"Button 1", "Button 2", "Button 3"};
ArrayInitialize(Variable,1);
double V[4];
ArrayFill(V,0,2,1);
ArrayFill(V,2,2,2);
数组迭代循环的正确写法
通常用 for 循环处理数组。静态已知大小可正反向迭代;动态数组应在循环前取大小存变量,避免每次迭代调用 ArraySize() 拖慢速度。若算法允许反向,可省大小变量,因 ArraySize 只调用一次。正向且频繁取大小是典型性能反模式。
int Size=ArraySize(Var);
for(int i=0; i<Size; i++){ /* ... */ }
for(int i=ArraySize(Variable)-1; i>=0; i--){ /* ... */ }
多维数组结构与第一维度动态性
MQL5支持最多4维数组。仅第一维可静态或动态,其余必须静态。如 double Variable[][3][3] 可用 ArrayResize 改第一维。ArraySize 返回总元素数,不是第一维大小,需用 ArrayRange 或除法计算。多维初始化用嵌套花括号,ArrayInitialize/Fill 按一维序列处理。
double Variable[][3][3];
ArrayResize(Variable,3);
int Size=ArraySize(Variable)/9;
double V[][3][3]={{1,2,3},{4,5,6},{7,8,9}};
多维数组传参时除第一维外须明确大小,因此无法写通用维度计算函数。推荐用常量定义维度,在初始化阶段算好。若需变其他维度,可用OOP或更大数组加变量模拟。
函数传参:数组只能引用传递
与变量不同,数组只能通过引用(&)传给函数,函数内直接操作原数组,改动会影响外部。普通变量按值传无法被函数修改,加 & 后可改。传递数组自变量要写括号表明是数组引用。
void Func(double &arg[]){ }
double var[][3][3];
void Func(double &arg[][3][3]){ }
文件保存与载入的维度陷阱
保存一维数组先写总大小再写数组;载入时读大小、Resize、读数组。多维载入需算第一维大小,如三维且后两维常量SIZE1、SIZE2时,第一维=总/(SIZE1*SIZE2)。注意 Var[2][3] 与 Var[3][2] 总元素同,若要区分需存更多元信息。
bool LoadArrayFromFile3(string FileName,double &Array[][SIZE1][SIZE2]){
int h=FileOpen(FileName,FILE_BIN|FILE_READ);
if(h==-1)return(false);
int SizeTotal=FileReadInteger(h,INT_VALUE);
int Elements=SIZE1*SIZE2;
int Size=SizeTotal/Elements;
ArrayResize(Array,Size);
FileReadArray(h,Array);
FileClose(h);
return(true);
}
动态数组扩容性能:逐次加1 vs 块扩容
不确定大小时动态数组很有用。若每加一个订单就 ArrayResize(ar,i+1) 逐次加1,速度极慢(测试数秒);用块扩容(如预留1024,满再扩)近乎瞬时。附 CDynamicArray 类封装了块扩容,应在 Include 文件夹使用。
class CDynamicArray{
private:
int m_ChunkSize; int m_ReservedSize; int m_Size;
public:
double Element[];
void CDynamicArray(int ChunkSize=1024){ m_Size=0; m_ChunkSize=ChunkSize; m_ReservedSize=ChunkSize; ArrayResize(Element,m_ReservedSize); }
void AddValue(double Value){ m_Size++; if(m_Size>m_ReservedSize){ m_ReservedSize+=m_ChunkSize; ArrayResize(Element,m_ReservedSize); } Element[m_Size-1]=Value; }
int Size(){ return(m_Size); }
};
索引编排顺序与ArraySetAsSeries陷阱
ArraySetAsSeries(ar,true) 设逆序索引,新元素加开头;false 正常序加末尾。注意:Resize 后再 SetAsSeries 会导致元素移位且可能引入随机值(用户评论证实)。正确顺序是先 Resize 再 SetAsSeries。逆序索引常用于EA复制价格数据,从右向左数柱。
double ar[]; ArrayResize(ar,2); ar[0]=1; ar[1]=2; ArraySetAsSeries(ar,true); ArrayResize(ar,3); ar[0]=3; Alert(ar[0]," ",ar[1]," ",ar[2]);
复制、排序与搜索标准函数
ArrayCopy 可整体或局部复制,自动扩动态数组;也可同数组内移位。ArraySort 排一维数组;ArrayBsearch 二分搜须先排序,返回匹配或最接近较小值索引,找不到超范围返0或末位。无序只能遍历。ArrayMaximum/Minimum 返极值索引,可限范围。
double ar1[]={1,2,3}; double ar2[]; ArrayCopy(ar2,ar1);
double ar[]={1,3,2,5,4}; ArraySort(ar);
int index=ArrayBsearch(ar,3);
int MaxIndex=ArrayMaximum(ar,5,3);
用OOP实现灵活多维数组
原文提供 CArrayBase 基类与 CDim/CArr 子类,可构造每第一维元素含不同长度第二维的锯齿数组。基类析构删对象,子类构造按大小Resize。虽非通用,但解决静态多维限制。读者也可借模板类 CDynamicArray<T> 泛型化。
class CArrayBase{
public: CArrayBase *D[]; double V[];
void ~CArrayBase(){ for(int i=ArraySize(D)-1;i>=0;i--) if(CheckPointer(D[i])==POINTER_DYNAMIC) delete D[i]; }
};