MQL4: Массивы и циклы » Элитный трейдер
Элитный трейдер


MQL4: Массивы и циклы

6 августа 2020 Trade Like A Pro Лосев Юрий
В сегодняшнем уроке, по вашим просьбам, мы подробно разберём работу с массивами и циклами посредством языка MQL4.

Циклы служат для многократного повторения какого-то участка кода, а массивы – для хранения неограниченного количества структур данных с их последующей обработкой в цикле.

Начнём с циклов. Поскольку обращение к элементам массива, как правило, осуществляется с использованием циклов, то научимся сначала работать с ними, а потом перейдем к массивам.

Циклы
Цикл while

Синтаксис цикла while в MQL4 имеет следующий вид:

[quote]while (<условие>)
{
<тело цикла>;
}[/quote]

Тело цикла в данном случае будет выполняться, пока истинно условие в круглых скобках. Если это условие на момент выполнения цикла окажется ложным, то тело цикла не выполнится ни одного раза. В качестве примера выведем в журнал терминала целые числа от 1 до 5, каждое с новой строки. Код будет выглядеть так:

[quote]int i = 1;
while (i <= 5)
{
Print( i );
i++;
}[/quote]

Исходя из условий задачи, мы сначала определили целочисленную переменную i и присвоили ей значение 1. В операторе цикла while проверяем, меньше или равно 5 значение переменной i. Если да, то выполняется тело цикла: значение печатается в журнал и увеличивается на единицу. После первой итерации в журнал выведено число «1», а значение переменной i равно двум. Спустя несколько итераций переменная i стала равна шести, условие i<=5 ложно. Следовательно, тело цикла выполняться больше не будет.

Цикл for

Этот вид цикла работает аналогично уже рассмотренному while. Здесь тело цикла выполняется, пока некое условие истинно. Однако в самом операторе можно задать действия, которые совершаются до начала цикла (например, инициализировать переменную-счётчик), а также после выполнения каждой итерации (увеличение или уменьшение счётчика). Такой вид цикла предназначен прежде всего для ситуаций, когда нам заранее известно число итераций. Синтаксис цикла for в языке MQL4 таков:

[quote]for(выражение1; выражение2; выражение3)
{
<тело цикла>;
}[/quote]

выражение1 – инициализация цикла, как правило, это объявление и инициализация счётчика итераций;
выражение2 – условие продолжения цикла, пока оно истинно, тело цикла выполняется;
выражение3 – выражение, вычисляемое после каждой итерации, обычно здесь происходит изменение счётчика итераций.
Рассмотрим цикл for на следующем примере: найдём сумму целых чисел от 1 до 1000.

[quote]int sum=0; // переменная для суммы чисел
for(int i=1; i<=1000; i++)
sum+=i; // прибавляем к текущему значению суммы очередное число
Print("Сумма чисел от 1 до 100: ",i);[/quote]

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

В операторе for переменная i выступает в качестве счётчика итераций. Мы объявили и проинициализировали её начальным значением «1». Нам требуется, чтобы цикл выполнялся 1000 раз, то есть пока переменная i меньше или равна 1000. Условие продолжения цикла примет вид i<=1000. После каждой итерации нам надо увеличить счётчик на единицу. Запишем через точку с запятой i++.

Тело цикла представляет всего один оператор sum+=i, что эквивалентно записи sum=sum+i. Поэтому операторные скобки «{ }» здесь не нужны. В теле цикла значение переменной sum увеличивается на значение переменной i, в которой на текущей итерации содержится очередное число от 1 до 1000. В итоге после тысячи выполнений тела цикла переменная sum будет содержать сумму всех целых чисел от 1 до 1000.

Любое из трех или все выражения в операторе for(выражение1; выражение2; выражение3) могут отсутствовать. Нельзя опускать только разделяющие выражения точки с запятыми, то есть в записи всегда должны быть оба символа «;». Например, запись for(;;) представляет собой бесконечный цикл. Выражение1 и выражение3 могут состоять из нескольких выражений, объединенных оператором запятая «,».

Решим такую задачу. Напечатаем в журнал числа от 1 до 5 и от 5 до 1 одновременно в одном цикле. При этом тело цикла должно состоять только из одного вызова функции Print().

[quote] for(int i=1, j=5; i<=5; i++, j--)
Print(i," ",j);[/quote]

Как видите, тело цикла выполняется 5 раз, при этом переменная i изменяется от 1 до 5, а переменная j от 5 до 1. Результат выполнения выглядит следующим образом:

[quote]5 1
4 2
3 3
2 4
1 5[/quote]

Заметьте, что наряду с инкрементом, то есть увеличением переменной i на единицу, здесь мы использовали и декремент, то есть уменьшали на единицу переменную j.

Запишем вывод в цикле пяти целых чисел от 1 до 5 в журнал следующим образом:

[quote]int n=1;
for(;n<=5;)
{
Print(n);
n++;
}[/quote]

Здесь мы объявили переменную-счётчик до оператора цикла, а её инкрементацию поместили в тело цикла.

Счётчик в цикле for может увеличиваться или уменьшаться на любое значение, а не только на единицу. Тем самым можно реализовать цикл с шагом. Выведем, например, в журнал все нечётные числа от 1 до 10. Для этого будем увеличивать счётчик после каждой итерации на 2, начиная с единицы:

[quote]for(int i=1; i<=10; i+=2)
Print(i);[/quote]

В данном случае переменная-счётчик последовательно примет значения 1, 3, 5, 7, 9, 11, а в журнал выведет 1, 3, 5, 7, 9.

Цикл do while

Как отмечалось ранее, если условие продолжения цикла c предусловием (while) изначально ложно, то тело цикла не выполнится ни разу. Однако есть задачи, которые требуют выполнения цикла по крайней мере однократно. Для этих целей существует цикл, тело которого выполнится хотя бы один раз независимо от условия. Синтаксис цикла do while или, как его еще называют, цикл с постусловием в MQL4 имеет следующий вид:

[quote]do
{
<тело цикла>;
}
while (<условие>);[/quote]

Выполним ту же задачу с выводом целых чисел от 1 до 5 с помощью цикла do while:

[quote]int i=1;
do
{
Print(i);
i++;
}
while(i<=5);[/quote]

Сначала мы объявили переменную-счётчик i и инициализировали её значением 1 согласно условию задачи. Затем сразу выполнилось тело цикла. В журнал было выведено значение переменной i, которое на тот момент было равно единице, следом значение переменной i увеличилось на единицу, а уже потом состоялась проверка условия i<=5. Так как 2 меньше 5, то цикл продолжился, пока значение переменной i не стало равняться шести. Обратите внимание, что инкремент счётчика в теле цикла мы производим после вывода его значения в журнал. Если мы будем делать это до вывода в журнал, то нам необходимо уменьшить начальное значение счётчика и изменить условие. В этом случае цикл будет выглядеть так:

[quote]int i=0;
do
{
i++;
Print(i);
}
while(i<5);[/quote]

Любопытно, что в языке MQL4 операцию инкремента можно выполнять прямо в вызове функции, при этом надо помнить, что постфиксный инкремент (i++) увеличивает значение переменной на единицу сразу после использования этой переменной в выражении, а префиксный инкремент (++i) увеличивает значение переменной на единицу непосредственно перед использованием этой переменной в выражении. Теперь наши примеры можно записать компактнее:

[quote]int i=1;
do
Print(i++);
while(i<=5);[/quote]

Здесь функция Print() выводит в журнал значение переменной i, а следом происходит увеличение i на единицу (инкремент):

[quote]int i=0;
do
Print(++i);
while(i<5);[/quote]

При вызове функции Print() сначала происходит увеличение переменной i на единицу, а потом значение i выводится в журнал.

Разберём пример посложнее. Напишем советник, который после прикрепления его к графику начинает обрабатывать поступающие тики не сразу, а спустя какое-то время, заданное нами в секундах. При этом советник должен сообщать с помощью комментария на графике, сколько секунд осталось до его запуска. Очевидно, что такую задержку надо поместить внутрь обработчика OnInit(), так как эта функция выполняется при инициализации советника.

[quote]//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
uint begin=GetTickCount(),// запоминаем значение функции GetTickCount()
passed_seconds, // прошло секунд
pause=10; // пауза в секундах
Comment(""); // очищаем комментарий на графике, выводя пустую строку

do
{
passed_seconds=(GetTickCount()-begin)/1000;// прошло секунд со старта
Comment("Советник начнёт работу через:",pause-passed_seconds);
}
while(passed_seconds5) break;
}[/quote]

Здесь мы имеем бесконечный цикл while, так как условие в круглых скобках всегда истинно. В теле цикла осуществляется вывод в журнал текущего значения переменной i и её инкрементация. Как только значение i станет равным шести, оператор break прервёт выполнение цикла. Тут стоит сделать одно важное замечание. Если цикл предполагает большое число итераций или была допущена ошибка, из-за которой цикл стал бесконечным, программа может не отвечать, «зависнуть». Чтобы избежать такой ситуации, в условие продолжения цикла добавляют вызов функции IsStopped(), которая возвращает истину (true), если от терминала поступила команда завершить выполнение mql4-программы (например, была нажата кнопка «Стоп» при работе в тестере). Учитывая это, наш пример будет правильнее записать так:

[quote] while(!IsStopped)
{
Print(i);
i++;
if(i>5) break;
}[/quote]

Это всё тот же бесконечный цикл, но выполняется он, пока не будет прерван оператором break или пока не поступит команда от терминала на завершение программы.

Надо заметить, что язык MQL4 позволяет прерывать цикл внутри функции еще и оператором return. Например:

[quote]void PrintNumber()
{
int i=1;
while(!IsStopped)
{
Print(i);
i++;
if(i>5) return;
}
}[/quote]

В данном случае оператор return прервёт цикл и передаст управление в то место программы, откуда была вызвана функция PrintNumber().

Оператор continue прерывает текущую итерацию цикла и начинает новую. Все операторы в теле цикла, идущие за оператором continue, не будут выполнены. Для иллюстрации работы оператора немного модифицируем нашу задачу с выводом в журнал целых чисел от 1 до 5. Выведем все числа, кроме 2.

[quote]for(int i=1; i<=5; i++)
{
if(i==2) continue;
Print(i);
}
1
2
3
4
5[/quote]

Вывод числа «2» будет пропущен.

Оператор continue удобно применять, если тело цикла довольно большое. Можно не усложнять конструкции ветвления внутри цикла, а сразу пропустить все последующие операторы и перейти к новой итерации.

Массивы

Массивы присутствуют практически во всех языках программирования, и MQL4 не стал исключением. По сути, массив – это совокупность переменных одного типа с одним именем, но при этом каждая переменная имеет свой уникальный номер. Например, нам нужны три целочисленные переменные. Мы можем объявить их следующим образом:

[quote]int Var1, Var2, Var3;[/quote]

В таком случае к каждой переменной можно обращаться по её уникальному имени. Например, присвоим переменным какие-то значения:

[quote]Var1=35;
Var2=8;
Var3=21;[/quote]

С другой стороны, переменные можно задать как массив из трёх элементов:

[quote]int Var[3];[/quote]

Обращаться же к этим переменным следует по их общему имени и номеру в массиве:

[quote]Var[0]=35;
Var[1]=8;
Var[2]=21;[/quote]

Номер переменной в массиве называют индексом (от латинского index – указательный палец). При объявлении массива в квадратных скобках указывается его размер (количество элементов), а при обращении к переменной – индекс элемента.

Обратите внимание, что нумерация элементов в массиве начинается с нуля, а не с единицы. Это обстоятельство часто служит причиной ошибок и вызывает вопросы, особенно у начинающих программистов. Массив, как и обычная переменная, – это область памяти, имеющая своё начало – адрес. В случае с массивами доступ к каждому элементу по индексу задаётся как смещение от начала. Тогда адрес первого элемента совпадает с адресом всего массива, то есть смещение равно нулю. Адрес второго элемента – это адрес первого плюс одно смещение на размер первого элемента. Адрес второго – это два смещения и т. д.

Статические и динамические массивы

Размер статических массивов устанавливается на этапе компиляции и больше в ходе выполнения программы не меняется. Пример объявления статического массива:

[quote]int Array[10];[/quote]

Здесь мы объявили массив из десяти целочисленных элементов, память под которые выделит компилятор.

Массивы, размер которых можно изменять в процессе выполнения программы, называются динамическими. Вместе с изменением размера массива память либо выделяется, либо освобождается динамически. Пример объявления динамического массива:

[quote]int Array[];[/quote]

Получается, если в квадратных скобках не указан размер массива, то он будет объявлен как динамический. В данном случае мы объявили динамический массив целочисленных элементов. Чтобы использовать массив, надо задать его размер с помощью функции ArrayResize():

[quote]ArrayResize(Array,10);[/quote]

Теперь массив Array состоит из 10 элементов с индексами от 0 до 9.

У данной функции есть третий необязательный параметр reserve_size. С помощью него можно задать размер массива «с запасом». Например, первый вызов функции в виде ArrayResize(Array, 10, 1000) увеличит размер массива до 10 элементов, но физически память под массив будет выделена, как если бы он содержал 1010 элементов. То есть память под 1000 элементов оказывается в резерве. Теперь при увеличении или уменьшении размера массива в пределах 1010 элементов физического распределения памяти не будет. Если же размер увеличить, скажем, до 1011, то выделится еще 1000 резервных элементов, и так далее. Поскольку физическое распределение памяти – процесс достаточно медленный, то в случае, если предполагается частое изменение размера массива в программе, третий параметр функции ArrayResize() будет весьма полезен.

В языке MQL4 все элементы массива инициализируются при его объявлении нулевым значением: для числовых массивов – это ноль, для строковых – пустая строка, для массива логических значений – это false. Однако есть ситуации, когда массив надо проинициализировать определёнными значениями. Для статических массивов это можно сделать в момент объявления массива следующим образом:

[quote]int Numbers[5] = {3, 21, 72, 1, 8};[/quote]

Здесь каждому из пяти элементов с индексами от 0 до 4 присваиваются по порядку значения в фигурных скобках. Можно проинициализировать схожим образом все элементы массива одним значением:

[quote]int Numbers[5] = {82};[/quote]

Все пять элементов массива примут значение 82.

Динамические массивы инициализировать таким образом нельзя. Даже если вы объявите массив:

[quote]int Numbers[] = {3, 21, 72, 1, 8};[/quote]

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

Чтобы инициализировать динамический массив, можно использовать функции ArrayInitialize() или ArrayFill(). Первая заполняет указанным значением весь массив, вторая может заполнить только часть массива. Но таким образом можно инициализировать или заполнить лишь массивы простых типов, массив, например, строк придётся заполнять в цикле, перебирая все элементы по индексу и присваивая каждому нужное значение. Как это сделать, поговорим в следующем разделе.

Использование циклов при работе с массивами

Поскольку элементы массива последовательно пронумерованы, то обработку массивов удобно вести с использованием циклов: производить действия над всеми элементами массива или его частями, искать элемент с нужными характеристиками, упорядочивать элементы внутри массива по какому-то критерию. Наиболее часто встречающаяся операция, применяемая к массиву – это перебор в цикле его значений, которую называют итерацией по массиву.

Решим задачу.

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

Для обработки слов создадим массив строк и запишем слова в него:

[quote]string Words[]={"машина", "пицца", "вода", "ножницы", "кот", "батарейка", "удочка", "гром", "стол", "флаг", "телефон", "зонт", "документ"};
int size=ArraySize(Words);
int n=0;

for(int i=0; i5)
{
Print(Words[i]);
n++;
}
}
if(n>0) Print("Количество слов, длина которых больше пяти: ",n);[/quote]

В этом примере нам понадобилась дополнительная переменная size, в которую мы поместили найденный с помощью функции ArraySize() размер массива. Было бы нерационально на каждой итерации вызывать функцию, чтобы сравнить полученное значение со счётчиком. Хотя в данном случае неважно, в каком направлении обходить элементы массива. Поэтому можно обойтись и без дополнительной переменной, обходя массив в обратном порядке:

[quote]int n=0;
for(int i=ArraySize(Words)-1; i>=0; i--)
if(StringLen(Words[i])>5)
{
Print(Words[i]);
n++;
}
if(n>0) Print("Количество слов, длина которых больше пяти: ",n);
Следующая задача: удалить элемент с указанным индексом из динамического массива целых чисел.

double Array[]; // подготовим массив для демонстрации работы алгоритма

int size=21; // зададим ему размер в 21 элемент
int removed_element=17; // индекс удаляемого элемента

Print("Новый Размер:", ArrayResize(Array,size));

for(int i=0; i=0; i--) // выведем в журнал все элементы массива
{
Print("Array[",i,"]=",Array[i]);
}

}
else
Print("Индекс удаляемого элемента находится за пределами массива");[/quote]

Передача массива в функцию

Массивы можно передавать в функции только по ссылке, то есть в функцию передаётся только адрес существующего массива, а не его копия. Функция не может вернуть и сам массив. Она только производит действия над массивом в целом или над его отдельными элементами. Таким образом, если аргумент функции представляет собой массив, то перед его именем необходимо писать знак «&» (амперсанд), как знак того, что аргумент передаётся по ссылке, а после имени – пустые квадратные скобки «[]», указывающие на то, что аргумент представляет из себя массив.

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

[quote]double Array[]; // подготовим массив для демонстрации работы нашей будущей функции
int size=21;
ArrayResize(Array,size);

for(int i=0; i=0; i--) // выведем в журнал все элементы массива
Print("Array[",i,"]=",Array[i]);

Напишем функцию, которая будет удалять указанный элемент.
//+------------------------------------------------------------------+
//| Удаляет элемент динамического массива с указанным индексом |
//| и возвращает "true", если удаление прошло удачно, в противном |
//| случае возвращает false |
//+------------------------------------------------------------------+
bool RemoveFromArray(double &array[], int index)
{
int size=ArraySize(array);

if(index

http://tradelikeapro.ru/ (C)
Не является индивидуальной инвестиционной рекомендацией
При копировании ссылка обязательна Нашли ошибку: выделить и нажать Ctrl+Enter