Оператор цикла for

Практический курс по C/C++: https://stepik.org/course/193691

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

Смотрите, в практике программирования очень часто возникают задачи, когда нужно организовать циклы по следующей схеме:

То есть, перед циклом мы выполняет некоторую инициализацию переменных, используемых затем в цикле. Далее идет условие цикла, если оно истинно, то выполняются операторы, записанные внутри цикла. И в конце после основной группы операторов, выполняются некоторые изменения переменных для новой итерации. Например, ранее мы использовали эту схему в цикле while следующим образом:

/* Инициализация */
int n;
int s = 0;
 
/* Цикл с предусловием */
while(n > 0) {
         s += n*n;    /* Операторы тела цикла */
         n--;         /* Изменение значений */
}

Так вот, чтобы иметь возможность в программах записывать подобные циклы в более краткой форме, и был введен оператор for, который имеет следующий синтаксис:

for([инициализация]; [условие]; [изменение значений])
    оператор;

или

for([инициализация]; [условие]; [изменение значений]) {
    операторы;
}

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

Давайте перепишем программу выше с циклом while через оператор for. Получим:

#include <stdio.h>
 
int main(void)
{
         /* Объявление переменных */
         int n, s;
 
         scanf("%d", &n);
 
         /* Цикл с предусловием for */
         for(s = 0; n > 0; --n)
                   s += n*n;    /* Операторы тела цикла */
 
         printf("s = %d\n", s);
         
         return 0;
}

Как видите, сам цикл имеет довольно краткую запись и визуально мы сразу можем выделить блок инициализации, блок проверки условия и блок изменения переменной. Если вам сейчас такая запись кажется несколько странной, то с опытом к ней очень быстро привыкаешь. И благодаря удобству оператор цикла for используется гораздо чаще оператора цикла while.

Давайте я приведу еще один пример с оператором цикла for для вычисления факториала числа:

#include <stdio.h>
 
int main(void)
{
         int n = 5, p = 1;
 
         for(int i = 1; i <= n; ++i)
                   p = p * i;
 
         printf("p = %d\n", p);
         
         return 0;
}

Я напомню, что:

n! = 1 ∙ 2 ∙ 3 ∙ … ∙ n

В программе мы определяем n = 5 и через цикл for находим факториал этого числа. Для этого задается вспомогательная переменная p с начальным значением 1 и в цикле for счетчик (переменная) i также с начальным значением 1. Обратите внимание, внутри блока инициализации допустимо объявлять переменную, которая ранее нигде не существовала. Причем, используемый мной компилятор gcc, создает эту переменную исключительно внутри цикла for и за его пределами она не существует. Однако другие компиляторы, работающие по другим стандартам, вполне могут определять такие переменные за пределами оператора for. Соответственно, доступ к ним сохраняется после выполнения цикла. Но для нас сейчас важно лишь то, что в блоке инициализации можно объявлять переменные и использовать их в теле цикла данного оператора.

Работает цикл очень просто. Сначала проверяется условие цикла, т.к. оно истинно, то выполняется текущая итерация – оператор «p = p * i;». После этого происходит увеличение счетчика i на единицу и снова проверяется условие цикла. В результате, мы получаем значение переменной p равное:

p = 1 ∙ 2 ∙ 3 ∙ 4 ∙ 5 = 120

А вот еще несколько вариаций записи этого же цикла:

         int i, p;
         for(i = 1, p = 1; i <= n; ++i)
                   p = p * i;

Здесь использована новая операция запятая для инициализации двух переменных. Или:

         int n = 5;
         int i = 1, p = 1;
         
         for(; i <= n; ++i)
                   p = p * i;

Здесь пустой блок инициализации, т.к. она прописана до оператора цикла. Или:

         int n = 5;
         int i = 1, p = 1;
 
         for(; i <= n;) {
                   p = p * i;
                   ++i;
         }

Здесь два пустых блока: инициализации и изменения значений. Или:

         int n = 5, i, p;
         for(i = 1, p = 1; i <= n; p = p * i, ++i)
                   { }

Вся логика вычислений прописана внутри оператора цикла for. Соответственно, в теле цикла ничего прописывать не нужно, но формально там все же должен быть указан хотя бы один оператор. Точка с запятой, как раз и воспринимается компилятором как оператор, который ничего не делает. Также в блоке изменения значений прописаны две операции, разделенные запятой. При этом сначала выполнится первая операция p = p * i и только потом вторая ++i. Это поведение для операции запятая строго определено в стандарте языка Си. Вычисление выражений, разделенных запятой, происходит слева-направо и никак иначе.

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

Раз уж мы затронули вопрос различных вариаций записей оператора цикла for, то приведу еще одну без каких-либо блоков:

for( ; ; );

Так тоже можно записывать. В этом случае цикл будет работать «вечно» пока мы или операционная система не прервет выполнение программы.

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

#include <stdio.h>
#include <stdlib.h>
 
int main(void)
{
         for(int x = rand() % 10; x != 0; x = rand() % 10)
                   printf("x = %d\n", x);
 
         return 0;
}

Как видите, в блоке инициализации формируется первое псевдослучайное значение в диапазоне [0; 9], а в блоке изменения следующее псевдослучайное значение из этого же диапазона. На каждой итерации на экран выводится полученное числовое значение, пока x не станет равен нулю.

На этом мы завершим первое знакомство с оператором цикла for. На следующем продолжим эту тему и поговорим об операторе цикла с постусловием do-while.

Практический курс по C/C++: https://stepik.org/course/193691

Видео по теме