拷贝和编辑数组
在本节中,我们将学习如何使用内置函数插入和移除数组元素,更改其顺序以及拷贝整个数组。
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],反之亦然。
该函数仅适用于固定大小或动态大小数组。不能使用此函数编辑时间序列(具有 时间序列的数组)。禁止在 target 和 source 参数中指定相同数组。
当插入到 fixed 数组中时,新元素将现有元素向右偏移,并将 count 个最右边的元素移位到数组外面。to 参数必须具有一个 0 到数组大小减 1 之间的值。
插入到动态数组中时,旧元素也被向右偏移,但不会消失,因为数组本身会扩展 count 个元素。to 参数必须具有一个 0 到数组大小之间的值。如果等于数组大小,则新元素被添加到数组末尾。
指定的元素从一个数组拷贝到另一个数组,即原始数组中的元素保持不变,这些元素在新数组中的“副本”成为独立实例,与“原始”元素无任何关联。
如果成功,该函数返回 true,如果出错,则返回 false。
我们来看一些示例 (ArrayInsert.mq5)。OnStart 函数提供了对不同配置的若干固定和动态数组的描述。
#define PRTS(A) Print(#A, "=", (string)(A) + " / status:" + (string)GetLastError())
|
为了方便起见,首先引入一个宏 PRTS,用于在调用测试指令之后立即显示错误代码(通过函数 GetLastError获取)。该宏是对我们所熟悉的 PRT 宏的轻量修改版本。
试图在不同配置的数组之间拷贝元素会产生错误 4006 (ERR_INVALID_ARRAY)。
// you can't mix 1D and 2D arrays
|
目标索引必须在数组内。
// target index 10 is out of the range or the array 'insert',
|
以下是成功的数组修改:
// copy second row from 'fixed', 'dynamic2Dx4' is allocated
|
日志中将会出现:
ArrayInsert(dynamic2Dx4,fixed,0,1,1)=true
|
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 函数不会偏移接收数组的现有元素,而是将新元素写入指定位置并覆盖旧元素。
所有索引以及元素的数量都是基于元素连续编号这一前提下设置,与数组中的维度数量及其配置无关。换言之,元素可从多维数组拷贝到一维数组,反之亦然;或者在具有不同大小的多维数组之间根据“更高”维度拷贝(参见示例)。
该函数适用于固定和动态数组,以及被指定为 指标缓冲区的时间序列数组。
允许从数组向其自身拷贝元素。但是如果 target 和 source 区域重叠,则你需要记住,迭代从左向右执行。
动态目标数组按需要自动扩展。固定数据保持其维度,被拷贝的内容必须适合数组大小,否则将会出错。
支持内置类型的数组以及具有简单类型字段的结构体数组。对于数字类型,如果源和目标类型不同,则函数将尝试转换数据。字符串数组只能被拷贝到字符串数组。类对象不允许拷贝,但对象指针可以被拷贝。
该函数返回拷贝的元素数量(出错则返回 0)。
在 ArrayCopy.mq5 脚本中,有该函数的若干用法示例。
class Dummy
|
具有对象的数组会生成编译错误,指出“包含对象的结构体或类不允许拷贝”,但指针可以被拷贝。
Dummy *pointers1[5], *pointers2[5];
|
具有简单类型字段的结构体数组也可以正常拷贝。
struct Simple
|
为进一步说明如何处理不同类型和配置的数组,定义了以下数组(包括固定和动态数组,以及具有不同维数的数组):
int dynamic[];
|
从 fixed 数组中的位置 1(编号 2)拷贝一个元素时,整行 4 个元素被分配到接收动态数组 dynamic2Dx4 中,并且由于仅拷贝了 1 个元素,其余三个元素将包含随机“垃圾”(以黄色高亮显示)。
PRTS(ArrayCopy(dynamic2Dx4, fixed, 0, 1, 1)); // 1 / status:0
|
接下来,我们从 fixed 数组拷贝所有元素,从第三个开始,拷贝到相同的 dynamic2Dx4 数组,但从位置 1 开始。由于拷贝了 5 个元素(fixed 数组中的总数是 8,减初始位置 3),并且它们于索引 1 处放置,将在接收数组中共占用 1 + 5 个位置,共 6 个元素。由于 dynamic2Dx4 数组每行有 4 个元素(在第二维度),可以仅为其是 4 的倍数个元素分配内存,即将分配 2 个额外元素,其中保留随机数据。
PRTS(ArrayCopy(dynamic2Dx4, fixed, 1, 3)); // 5 / status:0
|
当将多维数组拷贝到一维数组时,元素将以“平面”形式呈现。
PRTS(ArrayCopy(dynamic, fixed)); // 8 / status:0
|
将一维数组拷贝到多维数组时,元素根据接收数组的维数进行“扩展”。
PRTS(ArrayCopy(dynamic2Dx5, insert)); // 3 / status:0
|
在本例中,拷贝了 3 个元素,并且它们适配到长度为 5 个元素的一行(根据接收数组的配置)。为该系列的其余两个元素分配了内存,但未填充(包含“垃圾”)。
我们可以从另一个源覆写 dynamic2Dx5 数组,包括从一个不同配置的多维数组覆写。由于在接收数组中分配了两行,每行 5 个元素,而在源数组中分配了 2 行,每行 4 个元素,所以还有 2 个额外元素未填充。
PRTS(ArrayCopy(dynamic2Dx5, fixed)); // 8 / status:0
|
使用 ArrayCopy,可以更改固定接收数组中的元素。
PRTS(ArrayCopy(fixed, insert)); // 3 / status:0
|
在这里,我们已经覆写了 fixed 数组中的前三个元素。然后我们覆写后 3 个。
PRTS(ArrayCopy(fixed, insert, 5)); // 3 / status:0
|
无法拷贝到一个等于 fixed 数组长度的位置(在此情况下,动态目标数组将扩展)。
PRTS(ArrayCopy(fixed, insert, 8)); // 4006, ERR_INVALID_ARRAY
|
与其它类型的数组组合的字符串数组将会抛出错误:
PRTS(ArrayCopy(texts, insert)); // 5050, ERR_INCOMPATIBLE_ARRAYS
|
但是可以在字符串数组之间进行拷贝:
PRTS(ArrayCopy(texts, message));
|
不同数值类型的数组通过必要转换进行拷贝。
PRTS(ArrayCopy(insert, array, 1)); // 1 / status:0
|
在这里,我们已将数字 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
|
具有简单结构体的数组可由 ArrayRemove 函数成功处理,而具有析构函数的对象数组(即使具有隐式解构函数,如在 NotSoSimple 中)会导致错误:
void OnStart()
|
接下来,定义并初始化各种配置的数组。
int dynamic[];
|
从 fixed 数组中删除时,被移除片断之后的所有元素都将向左偏移。请务必要注意,数组的大小不会改变,因此,偏移的元素的副本会重复出现。
PRTS(ArrayRemove(fixed, 0, 1));
|
我们将二维数组 fixed 的第一维的一个元素以偏移 0(即初始行)移除。下一行的元素上移,保留在相同行中。
如果我们对动态数组(内容与 fixed 数组相同)执行相同操作,其大小将自动减少移除的元素数量。
PRTS(ArrayRemove(dynamic2Dx4, 0, 1));
|
在一维数组中,每个移除的元素对应一个值。例如,在 dynamic 数组中,当从索引 2 开始移除三个元素时,我们获得以下结果:
PRTS(ArrayRemove(dynamic, 2, 3));
|
值 3、4、5 已被移除,数组大小减少了 3。
bool ArrayReverse(void &array[], uint start = 0, uint count = WHOLE_ARRAY)
该函数可反转数组中指定元素的顺序。要反转的元素由 start 起始位置和 count 数量确定。如果 start = 0 且 count = WHOLE_ARRAY,则访问整个数组。
支持任意维数和类型的数组,无论是固定还是动态类型(包括 指标缓冲区中的时间序列)。数组可能包含对象、指针或结构体。对于多维数组,仅第一维被反转。
count 值必须在 0 到第一维度中的元素数量之间。请注意,小于 2 的 count 将不会有明显的影响,但它可用于统一算法中的循环。
如果成功,该函数返回 true,如果出错,则返回 false。
ArrayReverse.mq5 脚本可用于测试该函数。在其开头,定义了一个类,用于生成存储在数组中的对象。可以包含字符串和其它“复数”字段。
class Dummy
|
对象以序列号识别(在创建时赋予)。
void OnStart()
|
在应用 ArrayReverse 之后,我们获得预期的对象反转顺序。
PRTS(ArrayReverse(objects)); // true / status:0
|
接下来,准备了不同配置的数值数组,并以不同参数展开。
int dynamic[];
|
在后者情况下,值 start (2) 超过了第一维度的大小,因此出错。