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

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

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

1^2 + 2^2 + 3^2 + 4^2 + ... + n^2

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

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

цикл (пока не прошел час):
      прыгаем с горки

То есть, пока истинно условие, цикл работает, как только условие становится ложным (прошел час) цикл завершается. Ровно так работает цикл while, о котором и пойдет речь на этом занятии. Он имеет, следующее определение (синтаксис):

while(<условие>)
    оператор;

или

while(<условие>) {
    оператор 1;
    ...
    оператор N;
}

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

Давайте вернемся к исходной задаче – вычисления суммы квадратов целых чисел от 1 до n и посмотрим, как здесь можно использовать цикл while:

#include <stdio.h>
 
int main(void)
{
         int n;
         int s = 0;
 
         if(scanf("%d", &n) != 1) {
                   printf("Error input\n");
                   return 0;
         }
 
         while(n > 0) {
                   s += n*n;
                   n--;
         }
 
         printf("s = %d\n", s);
         
         return 0;
}

Вначале мы объявили две переменные n и s, причем, в s будет храниться сумма и она принимает начальное значение 0. Затем, запрашивается ввод в переменную n и если он некорректен, то программа завершается. Иначе запускается цикл while. В круглых скобках прописано условие продолжения цикла: пока n больше нуля. А в теле цикла определены два оператора: сначала к переменной s прибавляем квадрат наибольшего значения n, а потом, уменьшаем переменную n на единицу. В результате у нас будет образовываться следующая сумма:

s = n^2 + (n-1)^2 + … + 1^2

Как только значение n становится равным 0, условие цикла становится ложным и оператор while прекращает свою работу. Управление переходит к следующему оператору printf().

Давайте теперь несколько изменим нашу программу и запишем цикл while следующим образом:

         while(n-- > 0)
                   s += n*n;

Будет ли это тем же самым или программа станет работать по другому? Проверим это. Запустим и введем число 4. Получим сумму:

s = 14

Очевидно это не вся прежняя сумма:

s = 1^2 + 2^2 + 3^2 + 4^2 = 30

У нас не хватает последнего слагаемого 4^2 = 16. Почему так произошло? Все просто. Сначала выполняется сравнение переменной n с нулем (4 > 0) и после этого операция декремента. То есть, когда выполнение программы переходит к оператору «s += n*n;» тела цикла переменная n уже на единицу меньше. Возможно, некоторых из вас это удивит, так как операция декремента записана в постфиксной форме (после имени переменной), а значит, она должна выполняться в последнюю очередь. Но логика работы здесь несколько иная. Любое выражение, которое прописывается в условии, сначала полностью вычисляется и только затем осуществляется переход к телу цикла. И это всегда так. Этот важный момент нужно запомнить и знать. Именно поэтому переменная n гарантированно будет уменьшена на единицу при переходе к оператору «s += n*n;».

Кстати, последнее слагаемое в нашем примере будет 0^2 = 0. Поэтому (если ноль нам не нужен) правильнее было бы прописать эту условие так:

         while(--n > 0)
                   s += n*n;

Префиксная форма записи операции декремента сначала уменьшит значение n на единицу и только после этого будет осуществляться сравнение с нулем.

Давайте теперь немного усложним программу и сделаем ограничение на максимальное значение n на тот случай, если пользователь введет слишком большое число. Например, сделаем так, чтобы слагаемых было не больше 10:

         int i = 0;
         while(++i <= n && i <= 10)
                   s += i*i;

Условие в цикле while пришлось переписать с использованием дополнительной вспомогательной переменной i, которая, по сути, является счетчиком итераций. Ее первое значение при подсчете суммы будет равно 1, затем, 2 и так пока либо не дойдет до n, либо до 10.

Давайте внимательнее посмотрим на условие. Вначале записана операция инкремента в префиксной форме. Это означает, что значение переменной i увеличится на единицу и только потом выполнится операция сравнения. Мало того, здесь мы можем точно гарантировать, что операция инкремента (в любой форме записи: префиксной или постфиксной) отработает до перехода к следующей проверке i <= 10. Так заложено в стандарте языка Си. И это правило справедливо для всех логических связок:

&& и ||

где бы они ни использовались: в операторах циклов или условных операторах.

Благодаря этому мы можем четко понимать, как отработает данный цикл. Вначале обязательно увеличится значение переменной i на единицу, затем, проверится условие i <= n и только после этого (при необходимости) будет проверяться второе подусловие i <= 10.

Вообще, в качестве условия цикла while можно записывать любое выражение. Например, мы хотим считывать целые числа с клавиатуры, пока пользователь не введет 0. Сделать это можно следующим образом:

#include <stdio.h>
 
int main(void)
{
         int s = 0;
         int x = 1;
 
         while(scanf("%d", &x) == 1 && x != 0)
                   s += x;
 
         printf("s = %d\n", s);
         
         return 0;
}

Здесь цикл while будет работать до тех пор, пока пользователь либо вводит корректные данные (целые числа), либо введет число 0. В самом цикле выполняется суммирование введенных числовых значений, а после цикла отображение полученного результата с помощью функции printf().

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

         while(scanf("%d", &x) == 1 && x != 0)
                   if(x % 2 == 0)
                            s += x;

То есть, в теле цикла while можно прописывать абсолютно любые операторы языка Си. И, обратите внимание, в данном случае мы не указывали фигурные скобки, т.к. в теле цикла формально записан один оператор if, а для одного оператора фигурные скобки не обязательны. Хотя, при желании их можно записать:

         while(scanf("%d", &x) == 1 && x != 0) {
                   if(x % 2 == 0)
                            s += x;
         }

Программа от этого не изменится.

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

         while(scanf("%d", &x) == 1 && x != 0) {
                   int res = x % 2;
                   if(res == 0)
                            s += x;
         }

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

printf("res = %d\n", res);

то при компиляции возникнет ошибка, что переменная res не определена.

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

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

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

Видео по теме