拷贝和编辑数组

在本节中,我们将学习如何使用内置函数插入和移除数组元素,更改其顺序以及拷贝整个数组。

bool ArrayInsert(void &target[], const void &source[], uint to, uint from = 0, uint count = WHOLE_ARRAY)

该函数将指定数量的元素从源数组(即源)拷贝到目标 target 数组。插入到 target 数组中的插入位置由 to 参数中的索引设置。元素的起始拷贝索引(从 source 数组开始进行拷贝的位置)由 from 索引给定。参数 count 中的 WHOLE_ARRAY 常量 ((uint)-1) 指定传输源数组的所有元素。

所有索引和计数均相对于数组的第一维度。换言之,对于多维数组,插入不是按单个元素执行,而是按照由“更高”维度描述的整个配置执行。例如,对于一个二维数组,count 参数中的值 1 表示插入一个长度等于第二维度的向量(参见示例)。

因此,目标数组和源数组必须具有相同配置。否则将会出错,拷贝也将失败。对于一维数组,这不会成为限制,但对于多维数组,务必要注意第一维度以上各维度大小相等。尤其是,数组 [][4] 中的元素不能被插入到数组 [][5],反之亦然。

该函数仅适用于固定大小或动态大小数组。不能使用此函数编辑时间序列(具有 时间序列的数组)。禁止在 targetsource 参数中指定相同数组。

当插入到 fixed 数组中时,新元素将现有元素向右偏移,并将 count 个最右边的元素移位到数组外面。to 参数必须具有一个 0 到数组大小减 1 之间的值。

插入到动态数组中时,旧元素也被向右偏移,但不会消失,因为数组本身会扩展 count 个元素。to 参数必须具有一个 0 到数组大小之间的值。如果等于数组大小,则新元素被添加到数组末尾。

指定的元素从一个数组拷贝到另一个数组,即原始数组中的元素保持不变,这些元素在新数组中的“副本”成为独立实例,与“原始”元素无任何关联。

如果成功,该函数返回 true,如果出错,则返回 false

我们来看一些示例 (ArrayInsert.mq5)。OnStart 函数提供了对不同配置的若干固定和动态数组的描述。

#define PRTS(APrint(#A"=", (string)(A) + " / status:" + (string)GetLastError())
 
void OnStart()
{
   int dynamic[];
   int dynamic2Dx5[][5];
   int dynamic2Dx4[][4];
   int fixed[][4] = {{1234}, {5678}};
   int insert[] = {101112};
   int array[1] = {100};
   ...

为了方便起见,首先引入一个宏 PRTS,用于在调用测试指令之后立即显示错误代码(通过函数 GetLastError获取)。该宏是对我们所熟悉的 PRT 宏的轻量修改版本。

试图在不同配置的数组之间拷贝元素会产生错误 4006 (ERR_INVALID_ARRAY)。

   // you can't mix 1D and 2D arrays
   PRTS(ArrayInsert(dynamicfixed0)); // false:4006, ERR_INVALID_ARRAY
   ArrayPrint(dynamic); // empty
   // you can't mix 2D arrays of different configurations by the second dimension
   PRTS(ArrayInsert(dynamic2Dx5fixed0)); // false:4006, ERR_INVALID_ARRAY
   ArrayPrint(dynamic2Dx5); // empty
   // even if both arrays are fixed (or both are dynamic),
   // size by "higher" dimensions must match
   PRTS(ArrayInsert(fixedinsert0)); // false:4006, ERR_INVALID_ARRAY
   ArrayPrint(fixed); // not changed   
   ...

目标索引必须在数组内。

   // target index 10 is out of the range or the array 'insert',
   // could be 0, 1, 2, because its size = 3
   PRTS(ArrayInsert(insertarray10)); // false:5052, ERR_SMALL_ARRAY
   ArrayPrint(insert); // not changed   
   ...

以下是成功的数组修改:

   // copy second row from 'fixed', 'dynamic2Dx4' is allocated
   PRTS(ArrayInsert(dynamic2Dx4fixed011)); // true
   ArrayPrint(dynamic2Dx4);
   // both rows from 'fixed' are added to the end of 'dynamic2Dx4', it expands
   PRTS(ArrayInsert(dynamic2Dx4fixed1)); // true
   ArrayPrint(dynamic2Dx4);
   // memory is allocated for 'dynamic' for all elements 'insert'
   PRTS(ArrayInsert(dynamicinsert0)); // true
   ArrayPrint(dynamic);
   // 'dynamic' expands by 1 element
   PRTS(ArrayInsert(dynamicarray1)); // true
   ArrayPrint(dynamic);
   // new element will push the last one out of 'insert'
   PRTS(ArrayInsert(insertarray1)); // true
   ArrayPrint(insert);
}

日志中将会出现:

ArrayInsert(dynamic2Dx4,fixed,0,1,1)=true
    [,0][,1][,2][,3]
[0,]   5   6   7   8
ArrayInsert(dynamic2Dx4,fixed,1)=true
    [,0][,1][,2][,3]
[0,]   5   6   7   8
[1,]   1   2   3   4
[2,]   5   6   7   8
ArrayInsert(dynamic,insert,0)=true
10 11 12
ArrayInsert(dynamic,array,1)=true
 10 100  11  12
ArrayInsert(insert,array,1)=true
 10 100  11
 

 

bool ArrayCopy(void &target[], const void &source[], int to = 0, int from = 0, int count = WHOLE_ARRAY)

该函数将 source 数组的部分或全部拷贝到 target 数组。元素在 target 数组中的写入位置由 to 参数中的索引指定。元素的起始拷贝索引(从 source 数组开始进行拷贝的只)由 from 索引给定。count 参数中的 WHOLE_ARRAY 常量 (-1) 指定传输源数组的所有元素。如果 count 小于零或者大于从 from 位置到 source 数组末尾的剩余元素的数量,则会拷贝整个数组剩余部分。

不同于 ArrayInsert 函数,ArrayCopy 函数不会偏移接收数组的现有元素,而是将新元素写入指定位置并覆盖旧元素。

所有索引以及元素的数量都是基于元素连续编号这一前提下设置,与数组中的维度数量及其配置无关。换言之,元素可从多维数组拷贝到一维数组,反之亦然;或者在具有不同大小的多维数组之间根据“更高”维度拷贝(参见示例)。

该函数适用于固定和动态数组,以及被指定为 指标缓冲区的时间序列数组。

允许从数组向其自身拷贝元素。但是如果 targetsource 区域重叠,则你需要记住,迭代从左向右执行。

动态目标数组按需要自动扩展。固定数据保持其维度,被拷贝的内容必须适合数组大小,否则将会出错。

支持内置类型的数组以及具有简单类型字段的结构体数组。对于数字类型,如果源和目标类型不同,则函数将尝试转换数据。字符串数组只能被拷贝到字符串数组。类对象不允许拷贝,但对象指针可以被拷贝。

该函数返回拷贝的元素数量(出错则返回 0)。

ArrayCopy.mq5 脚本中,有该函数的若干用法示例。

class Dummy
{
   int x;
};
   
void OnStart()
{
   Dummy objects1[5], objects2[5];
 // error: structures or classes with objects are not allowed
   PRTS(ArrayCopy(objects1objects2));
   ...

具有对象的数组会生成编译错误,指出“包含对象的结构体或类不允许拷贝”,但指针可以被拷贝。

   Dummy *pointers1[5], *pointers2[5];
   for(int i = 0i < 5; ++i)
   {
      pointers1[i] = &objects1[i];
   }
   PRTS(ArrayCopy(pointers2pointers1)); // 5 / status:0
   for(int i = 0i < 5; ++i)
   {
      Print(i" "pointers1[i], " "pointers2[i]);
   }
 // it outputs some pairwise identical object descriptors
   /*
   0 1048576 1048576
   1 2097152 2097152
   2 3145728 3145728
   3 4194304 4194304
   4 5242880 5242880
   */

具有简单类型字段的结构体数组也可以正常拷贝。

struct Simple
{
   int x;
};
   
void OnStart()
{
   ...
   Simple s1[3] = {{123}, {456}, {789}}, s2[];
   PRTS(ArrayCopy(s2s1)); // 3 / status:0
   ArrayPrint(s2);
   /*
       [x]
   [0] 123
   [1] 456
   [2] 789
   */
   ...

为进一步说明如何处理不同类型和配置的数组,定义了以下数组(包括固定和动态数组,以及具有不同维数的数组):

   int dynamic[];
   int dynamic2Dx5[][5];
   int dynamic2Dx4[][4];
   int fixed[][4] = {{1234}, {5678}};
   int insert[] = {101112};
   double array[1] = {M_PI};
   string texts[];
   string message[1] = {"ok"};
   ...

fixed 数组中的位置 1(编号 2)拷贝一个元素时,整行 4 个元素被分配到接收动态数组 dynamic2Dx4 中,并且由于仅拷贝了 1 个元素,其余三个元素将包含随机“垃圾”(以黄色高亮显示)。

   PRTS(ArrayCopy(dynamic2Dx4fixed011)); // 1 / status:0
   ArrayPrint(dynamic2Dx4);
   /*
       [,0][,1][,2][,3]
   [0,]   2   1   2   3
   */
   ...

接下来,我们从 fixed 数组拷贝所有元素,从第三个开始,拷贝到相同的 dynamic2Dx4 数组,但从位置 1 开始。由于拷贝了 5 个元素(fixed 数组中的总数是 8,减初始位置 3),并且它们于索引 1 处放置,将在接收数组中共占用 1 + 5 个位置,共 6 个元素。由于 dynamic2Dx4 数组每行有 4 个元素(在第二维度),可以仅为其是 4 的倍数个元素分配内存,即将分配 2 个额外元素,其中保留随机数据。

   PRTS(ArrayCopy(dynamic2Dx4fixed13)); // 5 / status:0
   ArrayPrint(dynamic2Dx4);
   /*
       [,0][,1][,2][,3]
   [0,]   2   4   5   6
   [1,]   7   8   3   4
   */

当将多维数组拷贝到一维数组时,元素将以“平面”形式呈现。

   PRTS(ArrayCopy(dynamicfixed)); // 8 / status:0
   ArrayPrint(dynamic);
   /*
   1 2 3 4 5 6 7 8
   */

将一维数组拷贝到多维数组时,元素根据接收数组的维数进行“扩展”。

   PRTS(ArrayCopy(dynamic2Dx5insert)); // 3 / status:0
   ArrayPrint(dynamic2Dx5);
   /*
       [,0][,1][,2][,3][,4]
   [0,]  10  11  12   4   5
   */

在本例中,拷贝了 3 个元素,并且它们适配到长度为 5 个元素的一行(根据接收数组的配置)。为该系列的其余两个元素分配了内存,但未填充(包含“垃圾”)。

我们可以从另一个源覆写 dynamic2Dx5 数组,包括从一个不同配置的多维数组覆写。由于在接收数组中分配了两行,每行 5 个元素,而在源数组中分配了 2 行,每行 4 个元素,所以还有 2 个额外元素未填充。

   PRTS(ArrayCopy(dynamic2Dx5fixed)); // 8 / status:0
   ArrayPrint(dynamic2Dx5);
   /*
       [,0][,1][,2][,3][,4]
   [0,]   1   2   3   4   5
   [1,]   6   7   8   0   0
   */

使用 ArrayCopy,可以更改固定接收数组中的元素。

   PRTS(ArrayCopy(fixedinsert)); // 3 / status:0
   ArrayPrint(fixed);
   /*
       [,0][,1][,2][,3]
   [0,]  10  11  12   4
   [1,]   5   6   7   8
   */

在这里,我们已经覆写了 fixed 数组中的前三个元素。然后我们覆写后 3 个。

   PRTS(ArrayCopy(fixedinsert5)); // 3 / status:0
   ArrayPrint(fixed);
   /*
       [,0][,1][,2][,3]
   [0,]  10  11  12   4
   [1,]   5  10  11  12
   */

无法拷贝到一个等于 fixed 数组长度的位置(在此情况下,动态目标数组将扩展)。

   PRTS(ArrayCopy(fixedinsert8)); // 4006, ERR_INVALID_ARRAY
   ArrayPrint(fixed); // no changes

与其它类型的数组组合的字符串数组将会抛出错误:

   PRTS(ArrayCopy(textsinsert)); // 5050, ERR_INCOMPATIBLE_ARRAYS
   ArrayPrint(texts); // empty

但是可以在字符串数组之间进行拷贝:

   PRTS(ArrayCopy(textsmessage));
   ArrayPrint(texts); // "ok"

不同数值类型的数组通过必要转换进行拷贝。

   PRTS(ArrayCopy(insertarray1)); // 1 / status:0
   ArrayPrint(insert); // 10  3 12

在这里,我们已将数字 Pi 写入一个整数数组,因此接收到了值 3 (其替换了 11)。

bool ArrayRemove(void &array[], uint start, uint count = WHOLE_ARRAY)

该函数从数组的 start 索引开始删除指定数量的元素。数组可以是多维的,支持任何内置类型或字段为内置类型的结构体类型,但字符串除外。

start 索引和 count 数量指数组的第一维度。换言之,对于多维数组,删除不是按单个元素执行,而是按照由“更高”维度描述的整个配置执行。例如,对于一个二维数组,count 参数中的值 1 表示删除一个长度等于第二维度的整个系列(参见示例)。

start 必须在 0 和第一维度大小减 1 之间。

该函数不能应用于具有时间序列(内置 时间序列指标缓冲区中的时间序列)。

为测试该函数,我们准备了 ArrayRemove.mq5 脚本。特别要注意的是,该脚本定义了 2 个结构体:

struct Simple
{
   int x;
};
   
struct NotSoSimple
{
   int x;
   string s// a field of type string causes the compiler to make an implicit destructor
};

具有简单结构体的数组可由 ArrayRemove 函数成功处理,而具有析构函数的对象数组(即使具有隐式解构函数,如在 NotSoSimple 中)会导致错误:

void OnStart()
{
   Simple structs1[10];
   PRTS(ArrayRemove(structs105)); // true / status:0
   
   NotSoSimple structs2[10];
   PRTS(ArrayRemove(structs205)); // false / status:4005,
                                      // ERR_STRUCT_WITHOBJECTS_ORCLASS
   ...

接下来,定义并初始化各种配置的数组。

   int dynamic[];
   int dynamic2Dx4[][4];
   int fixed[][4] = {{1234}, {5678}};
   
   // make 2 copies
   ArrayCopy(dynamicfixed);
   ArrayCopy(dynamic2Dx4fixed);
   
   // show initial data
   ArrayPrint(dynamic);
   /*
   1 2 3 4 5 6 7 8
   */
   ArrayPrint(dynamic2Dx4);
   /*
       [,0][,1][,2][,3]
   [0,]   1   2   3   4
   [1,]   5   6   7   8
   */

从 fixed 数组中删除时,被移除片断之后的所有元素都将向左偏移。请务必要注意,数组的大小不会改变,因此,偏移的元素的副本会重复出现。

   PRTS(ArrayRemove(fixed01));
   ArrayPrint(fixed);
   /*
   ArrayRemove(fixed,0,1)=true / status:0
       [,0][,1][,2][,3]
   [0,]   5   6   7   8
   [1,]   5   6   7   8
   */

我们将二维数组 fixed 的第一维的一个元素以偏移 0(即初始行)移除。下一行的元素上移,保留在相同行中。

如果我们对动态数组(内容与 fixed 数组相同)执行相同操作,其大小将自动减少移除的元素数量。

   PRTS(ArrayRemove(dynamic2Dx401));
   ArrayPrint(dynamic2Dx4);
   /*
   ArrayRemove(dynamic2Dx4,0,1)=true / status:0
       [,0][,1][,2][,3]
   [0,]   5   6   7   8
   */

在一维数组中,每个移除的元素对应一个值。例如,在 dynamic 数组中,当从索引 2 开始移除三个元素时,我们获得以下结果:

   PRTS(ArrayRemove(dynamic23));
   ArrayPrint(dynamic);
   /*
   ArrayRemove(dynamic,2,3)=true / status:0
   1 2 6 7 8
   */

值 3、4、5 已被移除,数组大小减少了 3。

bool ArrayReverse(void &array[], uint start = 0, uint count = WHOLE_ARRAY)

该函数可反转数组中指定元素的顺序。要反转的元素由 start 起始位置和 count 数量确定。如果 start = 0count = WHOLE_ARRAY,则访问整个数组。

支持任意维数和类型的数组,无论是固定还是动态类型(包括 指标缓冲区中的时间序列)。数组可能包含对象、指针或结构体。对于多维数组,仅第一维被反转。

count 值必须在 0 到第一维度中的元素数量之间。请注意,小于 2 的 count 将不会有明显的影响,但它可用于统一算法中的循环。

如果成功,该函数返回 true,如果出错,则返回 false

ArrayReverse.mq5 脚本可用于测试该函数。在其开头,定义了一个类,用于生成存储在数组中的对象。可以包含字符串和其它“复数”字段。

class Dummy
{
   static int counter;
   int x;
   string s// a field of type string causes the compiler to create an implicit destructor
public:
   Dummy() { x = counter++; }
};
   
static int Dummy::counter;

对象以序列号识别(在创建时赋予)。

void OnStart()
{
   Dummy objects[5];
   Print("Objects before reverse");
   ArrayPrint(objects);
   /*
       [x]  [s]
   [0]   0 null
   [1]   1 null
   [2]   2 null
   [3]   3 null
   [4]   4 null
   */

在应用 ArrayReverse 之后,我们获得预期的对象反转顺序。

   PRTS(ArrayReverse(objects)); // true / status:0
   Print("Objects after reverse");
   ArrayPrint(objects);
   /*
       [x]  [s]
   [0]   4 null
   [1]   3 null
   [2]   2 null
   [3]   1 null
   [4]   0 null
   */

接下来,准备了不同配置的数值数组,并以不同参数展开。

   int dynamic[];
   int dynamic2Dx4[][4];
   int fixed[][4] = {{1234}, {5678}};
   
   ArrayCopy(dynamicfixed);
   ArrayCopy(dynamic2Dx4fixed);
   
   PRTS(ArrayReverse(fixed)); // true / status:0
   ArrayPrint(fixed);
   /*
       [,0][,1][,2][,3]
   [0,]   5   6   7   8
   [1,]   1   2   3   4
   */
   
   PRTS(ArrayReverse(dynamic43)); // true / status:0
   ArrayPrint(dynamic);
   /*
   1 2 3 4 7 6 5 8
   */
   
   PRTS(ArrayReverse(dynamic01)); // does nothing (count = 1)
   PRTS(ArrayReverse(dynamic2Dx421)); // false / status:5052, ERR_SMALL_ARRAY
}

在后者情况下,值 start (2) 超过了第一维度的大小,因此出错。