Цикл for

Данный цикл реализуется инструкцией с ключевым словом for — отсюда и название. В обобщенном виде её можно описать так:

for ( [инициализация]; [условие]; [выражение] )
   тело цикла

В заголовке после слова 'for' в круглых скобках указываются:

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

Тело цикла — это простая или составная инструкция.

Все три компоненты заголовка являются опциональными и могут быть опущены в произвольном сочетании, включая и полное их отсутствие.

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

Цикл начинает выполняться, если после инициализации условие равно true, и продолжает выполняться, пока оно верно в начале каждой последующей итерации. Если же во время очередной проверки условие нарушается, происходит выход из цикла, то есть управление передается инструкции, которая написана после цикла и его тела. Если условие равно false перед началом цикла (после инициализации), он не выполнится ни разу.

Условие и выражение обычно включают переменные цикла.

Выполнение цикла означает выполнение его тела.

Наиболее распространенная форма цикла for подразумевает единственную переменную цикла, которая управляет количеством итераций. В следующем примере мы рассчитываем для чисел в массиве a их квадраты.

int a[] = {1234567};
const int n = ArraySize(a);
for(int i = 0i < n; ++i)
   a[i] = a[i] * a[i];
ArrayPrint(a);    // 1  4  9 16 25 36 49
// Print(i);      // error: 'i' - undeclared identifier

Выполнение данного цикла производится по следующим шагам:

  1. Создается переменная i с начальным значением 0.
  2. Проверяется условие, что переменная i меньше размера цикла n. Пока оно верно, цикл продолжается. Если оно нарушено, произойдет переход к инструкции с вызовом функции ArrayPrint.
  3. При истинности условия выполняются инструкции тела цикла, в данном случае, в i-й элемент массива записывается произведение исходного значения этого элемента на самого себя, т.е. значение каждого элемента заменяется на его квадрат.
  4. Происходит увеличение переменной i на 1.

Далее всё повторяется, начиная с шага 2. После выхода из цикла его переменная i уничтожается, и попытка обратиться к ней вызовет ошибку.

Выражение для шага 4 может быть произвольной сложности, а не только инкрементом переменной цикла. Например, для прохода только по четным или нечетным элементам можно было бы написать i += 2.

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

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

for(int i = 0j = n - 1i < n / 2; ++i, --j)
{
   int temp = a[i];
   a[i] = a[j];
   a[j] = temp;
}
ArrayPrint(a);    // 49 36 25 16  9  4  1

Вспомогательная переменная temp создается и удаляется при каждом проходе цикла, однако компилятор распределяет под неё память только один раз, как и для всех локальных переменных, при входе в функцию. Эта оптимизация хорошо работает для встроенных типов. Однако если в цикле будет описан объект пользовательского класса, то его конструктор и деструктор будут вызываться на каждой итерации.

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

Чтобы продемонстрировать возможность опускать компоненты заголовка, представим следующая задачу: необходимо найти количество элементов того же массива, сумма которых меньше 100. Для этого нам потребуется переменная-счетчик k, определенная до цикла, потому что она должна продолжить существование после его завершения. Также заведем переменную sum для подсчета суммы нарастающим итогом.

int k = 0sum = 0;
for( ; sum < 100; )
{
  sum += a[k++];
}
 
Print(k" "sum - a[k]); // 3 94

Таким образом, в заголовке не требуется делать инициализацию. Кроме того, счетчик k увеличивается с помощью постфиксного инкремента непосредственно в выражении, подсчитывающем сумму (при обращении к элементу массива). Поэтому нам не нужно и выражение в заголовке.

По завершении цикла мы выводим k и сумму за вычетом последнего прибавленного элемента, поскольку именно он переполнил наш лимит 100.

Обратите внимание, что мы используем составной блок, несмотря на то, что в теле цикла всего одна инструкция. Это полезно делать потому, что при разрастании программы всё уже готово для добавления дополнительных инструкций внутрь скобок. Кроме того, такой подход гарантирует единый стиль для всех циклов. Но выбор, в любом случае, за программистом.

В предельном, максимально сокращенном варианте заголовок цикла может выглядеть так:

for( ; ; )
{
   // ...       // периодические действия
   Sleep(1000); // приостановить работу программы на 1 секунду
}

Если в теле такого цикла нет инструкций, которые производили бы прерывание цикла по каким-то условиям, он будет выполняться постоянно. Способы прерывания и проверки условий мы изучим соответственно в разделах Переход break и Выбор if.

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

Скрипт StmtLoopsFor.mq5 содержит в конце бесконечный цикл, но только в целях демонстрации.

for( ; ; )
{
   Comment(GetTickCount());
   Sleep(1000); // 1000 мс
  
   // выйти из цикла можно только удалив скрипт по команде пользователя
   // после 3 секунд ожидания получим сообщение 'Abnormal termination'
}
Comment("");  // эта строка никогда не выполнится

В цикле раз в секунду выводится внутренний таймер компьютера (GetTickCount) с помощью функции Comment: значение высвечивается в левом верхнем углу графика. Прервать цикл может только пользователь, удалив весь скрипт с графика (кнопка "Удалить" в диалоге экспертов). Данный код не проверяет внутри цикла такие запросы пользователя на остановку, хотя для этих целей имеется встроенная функция IsStopped. Она возвращает true, если пользователь дал команду остановиться. В программе, особенно при наличии циклов и долгосрочных вычислений, желательно предусмотреть проверку значения этой функции и добровольно завершить цикл и всю программу по получении true. В противном случае терминал принудительно завершит скрипт после 3 секунд ожидания (с выводом в журнал "Abnormal termination"), что и случится в данном примере.

Более правильный вариант этого цикла должен быть таким:

for( ; !IsStopped(); ) // продолжаем, пока пользователь не прервал
{
   Comment(GetTickCount());
   Sleep(1000); // 1000 мс
}
Comment("");    // очистит комментарий

Однако данный цикл лучше было бы реализовать с помощью другой инструкции повторения while. По негласному правилу, цикл for следует использовать только при наличии очевидной переменной цикла и/или определенном заранее количестве повторов. В данном случае эти условия не выполняются.

Обычно переменные цикла являются целыми, хотя допускаются и другие типы, например, double. Это связано с тем, что сама логика работы цикла подразумевает нумерацию итераций. Кроме того, из целого индекса всегда можно рассчитать необходимые вещественные числа, причем с большей точностью. Например, следующий цикл производит перебор значений от 0.0 до 1.0 с шагом 0.01:

for(double x = 0.0x < 1.0x += 0.01) { ... }

Его можно заменить аналогичным циклом с целочисленной переменной:

for(int i = 0i < 100; ++i) { double x = i * 0.01; ... }

В первом случае, при сложениях x += 0.01 постепенно накапливается погрешность вычислений с плавающей запятой. Во втором случае каждое значение x получается за одну операцию i * 0.01, с максимально доступной точностью.

Переменным цикла принято давать следующие имена из одной буквы, например: i, j, k, m, p, q. Несколько имен требуется, когда циклы являются вложенными или внутри одного цикла высчитываются как прямой (увеличивающийся), так и обратный (уменьшающийся) индексы.

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

int table[10][10] = {0};
for(int i = 1i <= 10; ++i)
{
   for(int j = 1j <= 10; ++j)
   {
      table[i][j] = i * j;
   }
}
ArrayPrint(table);