Практический курс по 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 используют не
только для формирования каких-либо арифметических последовательностей в
программах. Спектр его применения куда шире. Здесь следует исходить из того,
что в блоках инициализации и изменения значений можно прописывать любые
допустимые конструкции. Например, такие:
#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