Копирование и редактирование массивов

В данном разделе мы узнаем, как с помощью встроенных функций можно вставлять и удалять элементы массивов, менять их порядок, а также копировать массивы целиком.

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

Функция вставляет в массив-приемник target указанное количество элементов из массива-источника source. Место вставки в массив target задается индексом в параметре to. Начальный индекс элемента, с которого начинается копирование из массива source, задается индексом from. Константа WHOLE_ARRAY ((uint)-1) в параметре count задает перенос всех элементов массива-источника.

Все индексы и количество относятся к первому измерению массивов. Иными словами, для многомерных массивов вставка производится не отдельными элементами, а всей конфигурацией, описанной "высшими" размерностями. Например, для двумерного массива значение 1 в параметре count означает вставку вектора длиной, равной второй размерности (см. пример).

Из этого следует, что конфигурации массива-приемника и массива-источника должны быть одинаковыми (иначе возникнет ошибка, и копирования не произойдет). Для одномерных массивов это не является ограничением, но для многомерных необходимо соблюдать равенство размеров по измерениям выше первого. В частности, элементы из массива [][4] нельзя вставить в массив [][5] и наоборот.

Функция применима только для массивов фиксированного или динамического размера. Изменить таймсерии (массивы с временными рядами) с помощью данной функции нельзя. Также не допускается указывать в параметрах target и source один и тот же массив.

При вставке в фиксированный массив новые элементы сдвигают имевшиеся элементы вправо и вытесняют 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};
   ...

Предварительно для удобства введен макрос, выводящий код ошибки (получаемый через функцию GetLastError) сразу после вызова тестируемой инструкции — PRTS. Это слегка модифицированная версия знакомого нам макроса PRT.

Попытки копирования элементов между массивами разной конфигурации заканчиваются ошибкой 4006 (ERR_INVALID_ARRAY).

   // нельзя смешивать массивы 1D и 2D
   PRTS(ArrayInsert(dynamicfixed0)); // false:4006, ERR_INVALID_ARRAY
   ArrayPrint(dynamic); // пусто
   // нельзя смешивать 2D массивы разных конфигураций по второй размерности
   PRTS(ArrayInsert(dynamic2Dx5fixed0)); // false:4006, ERR_INVALID_ARRAY
   ArrayPrint(dynamic2Dx5); // пусто
   // даже если оба массива фиксированные (или оба динамические),
   // размер по "высшим" размерностям должен совпадать
   PRTS(ArrayInsert(fixedinsert0)); // false:4006, ERR_INVALID_ARRAY
   ArrayPrint(fixed); // не изменен   
   ...

Целевой индекс должен находиться в пределах массива.

   // целевой индекс 10 выходит за пределы массива 'insert',
   // мог быть 0, 1, 2, т.к. его размер = 3 
   PRTS(ArrayInsert(insertarray10)); // false:5052, ERR_SMALL_ARRAY
   ArrayPrint(insert); // не изменен   
   ...

Далее идут успешные модификации массивов:

   // копируется второй ряд из 'fixed', 'dynamic2Dx4' распределяется
   PRTS(ArrayInsert(dynamic2Dx4fixed011)); // true
   ArrayPrint(dynamic2Dx4);
   // оба ряда из 'fixed' добавляются в конец 'dynamic2Dx4', он расширяется
   PRTS(ArrayInsert(dynamic2Dx4fixed1)); // true
   ArrayPrint(dynamic2Dx4);
   // под 'dynamic' выделяется память для всех элементов 'insert'
   PRTS(ArrayInsert(dynamicinsert0)); // true
   ArrayPrint(dynamic);
   // 'dynamic' расширяется на 1 элемент
   PRTS(ArrayInsert(dynamicarray1)); // true
   ArrayPrint(dynamic);
   // новый элемент вытолкнет последний из '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. Константа WHOLE_ARRAY (-1) в параметре count задает перенос всех элементов массива-источника. Если count меньше нуля или больше числа элементов, оставшихся от позиции from до конца массива source, копируется весь остаток массива.

В отличие от функции ArrayInsert, функция ArrayCopy не сдвигает имеющиеся элементы приемного массива, а записывает новые элементы в указанные позиции поверх старых.

Все индексы и количество элементов задаются с учетом сквозной нумерацией элементов вне зависимости от количества измерений в массивах и их конфигурации. Иными словами, элементы можно копировать из многомерных массивов в одномерные и обратно или между многомерными массивами с разными размерами по "высшим" размерностям (см. пример).

Функция работает с фиксированными и динамическими массивами, а также массивами временных рядов, назначенными в качестве индикаторных буферов.

Допустимо копировать элементы из массива в самого себя. Но если области target и source перекрываются, нужно иметь в виду, что обход делается слева направо.

Динамический массив-приемник при необходимости автоматически расширяется. Фиксированные массивы сохраняют свои размеры, причем копируемое должно уместиться в массиве, иначе возникнет ошибка.

Поддерживаются массивы встроенных типов и массивы структур с полями простых типов. При этом для числовых типов функция попытается выполнить конвертацию, если типы источника и приемника различаются.

Строковый массив можно скопировать только в строковый массив.

Объекты классов запрещены, однако можно копировать указатели на объекты.

Функция возвращает количество скопированных элементов (0 в случае ошибки).

В скрипте ArrayCopy.mq5 приведено несколько примеров использования функции.

class Dummy
{
   int x;
};
   
void OnStart()
{
   Dummy objects1[5], objects2[5];
   // ошибка: структуры или классы с объектами не разрешены
   PRTS(ArrayCopy(objects1objects2));
   ...

Массивы с объектами генерируют ошибку компиляции о том, что такие элементы не поддерживаются ("structures or classes containing objects are not allowed"). Зато можно копировать указатели.

   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]);
   }
   // выведет некие попарно одинаковые дескрипторы объектов
   /*
   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) в приемном динамическом массиве dynamic2Dx4 выделяется целый ряд из 4 элементов, и поскольку копируется только 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
   */

Копировать в позицию, равную длине фиксированного массива, не получится (динамический массив-приемник при этом расширился бы).

   PRTS(ArrayCopy(fixedinsert8)); // 4006, ERR_INVALID_ARRAY
   ArrayPrint(fixed); // без изменений

Строковые массивы в сочетании с массивами других типов выдадут ошибку:

   PRTS(ArrayCopy(textsinsert)); // 5050, ERR_INCOMPATIBLE_ARRAYS
   ArrayPrint(texts); // пусто

Но между строковыми массивами копирование возможно:

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

Массивы разных числовых типов копируются с необходимой конвертацией.

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

Здесь мы записали число Пи в целочисленный массив, и потому получили значение 3 (оно заменило 11).

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

Функция удаляет из массива указанное количество элементов начиная с индекса start. Массив может быть многомерным и иметь любой встроенный тип или тип структуры с полями встроенных типов, за исключением строк.

Индекс start и количество count относятся к первому измерению массивов. Иными словами, для многомерных массивов удаление производится не отдельными элементами, а всей конфигурацией, описанной "высшими" размерностями. Например, для двумерного массива значение 1 в параметре count означает удаление целого ряда длиной, равной второй размерности (см. пример).

Значение start должно лежать в пределах от 0 до размера первого измерения минус 1.

Функцию нельзя применять к массивам с временными рядами (встроенным таймсериям или буферам индикаторов).

Для проверки работы функции подготовлен скрипт ArrayRemove.mq5. В нем, в частности, определены 2 структуры:

struct Simple
{
   int x;
};
   
struct NotSoSimple
{
   int x;
   string s// поле типа string заставляет компилятор сделать неявный деструктор
};

Массивы с простой структурой могут обрабатываться функцией 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}};
   
   // делаем 2 копии
   ArrayCopy(dynamicfixed);
   ArrayCopy(dynamic2Dx4fixed);
   
   // показываем исходные данные   
   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
   */

При удалении из фиксированного массива, все элементы, находящиеся после удаляемого фрагмента, сдвигаются влево. Важно, что размер массива при этом не меняется и потому копии сдвинутых элементов оказываются в двух экземплярах.

   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 = 0, а count = WHOLE_ARRAY, обращается весь массив целиком.

Поддерживаются массивы произвольной размерности и типов, как фиксированные, так и динамические (включая временные ряды в буферах индикаторов). Массив может содержать объекты, указатели или структуры.

Для многомерных массивов разворот производится только по первому измерению.

Значение count должно быть в диапазоне от 0 до количества элементов в первом измерении. Учтите, что count меньше 2 не даст заметного эффекта, но это можно использовать в целях унификации циклов в алгоритмах.

Функция возвращает true в случае успеха или false в случае ошибки.

Для проверки функции был написан скрипт ArrayReverse.mq5. В его начале определен класс для порождения объектов, хранимых в массиве. Наличие строк и прочих "сложных" полей — не помеха.

class Dummy
{
   static int counter;
   int x;
   string s// поле типа string заставляет компилятор создать неявный деструктор
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)); // ничего не делает (count = 1)
   PRTS(ArrayReverse(dynamic2Dx421)); // false / status:5052, ERR_SMALL_ARRAY
}

В последнем случае значение start (2) превышает размер в первом измерении, поэтому возникает ошибка.