Операторы break, continue и goto

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

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

  • break – досрочное прерывание работы оператора цикла;
  • continue – пропуск итерации тела цикла;
  • goto – безусловный переход по метке.

Оператор break

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

#include <stdio.h>
 
int main(void)
{
         double x, s = 0.0;
 
         while(scanf("%lf", &x) == 1) {
                   if(x < 0)
                            break;
 
                   s += x;
         }
 
         printf("s = %.2f\n", s);
 
         return 0;
}

Вначале объявляются две вспомогательные переменные: x – для ввода текущего вещественного значения; s – для подсчета суммы вещественных положительных чисел. Далее запускается цикл while, который работает, пока вводятся корректные числовые значения. В теле цикла идет проверка, если введенное значение x отрицательное, то срабатывает оператор break. Этот оператор завершает работу цикла while и управление передается следующему оператору printf(). Если же введенное число неотрицательное, то выполняется подсчет суммы.

Как видите, все предельно просто. Для примера, если в этой программе убрать условие и записать оператор break в теле цикла, то цикл while гарантированно будет прерываться на первой же итерации.

Конечно, эту же программу можно было бы записать и без оператора break, изменив условие цикла:

         while(scanf("%lf", &x) == 1 && x >= 0) {
                   s += x;
         }

Возможно, даже любую программу можно составить без оператора break. Но его использование может заметно облегчить написание и понимание логики программы. Именно с этой целью он и введен в язык Си, да и во многие другие языки высокого уровня.

Отмечу здесь еще один распространенный способ прерывания работы цикла. Если вместо break записать оператор return:

         while(scanf("%lf", &x) == 1) {
                   if(x < 0)
                            return 0;
 
                   s += x;
         }

то для цикла while эффект будет такой же, как и от оператора break. Но, правда, есть и существенное отличие. Как мы уже знаем, оператор return завершает выполнение функции. В данном случае функции main(). Поэтому, как только пользователь введет отрицательное число, то программа просто завершится, результат на экран выведен не будет, так как все операторы после while уже не сработают. В этом ключевое отличие между break и return. Оператор break позволяет продолжить выполнение функции, и управление передается следующему оператору после цикла. А при return текущая функция завершается сразу. На самом деле, оператор return завершает работу функции и лишь, как следствие, прерывает работу цикла. По той же логике оператор return можно для завершения работы оператора switch, о котором мы с вами ранее уже говорили. И вообще, с помощью return можно прервать работу чего угодно в пределах функции.

Оператор continue

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

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

Начало вам уже знакомо, а дальше записан цикл while, который работает пока пользователь вводит корректные данные или не введет число 0. Затем, в теле цикла стоит проверка на четность введенного числа x и если оно четно, то выполняется оператор continue. Как только встретился этот оператор, все остальные после него операторы тела цикла пропускаются и мы переходим к следующей итерации цикла while. В результате, сумма будет вычисляться только для нечетных значений.

Конечно, опят же, эту программу можно было бы реализовать и без оператора continue, например, так:

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

И это вполне рабочий вариант. Здесь все как и с оператором break. Оператор continue введен для удобства программирования и его следует использовать тогда, когда он позволяет упростить логику программы и сделать ее более читабельной.

Оператор continue, равно как и оператор break можно использовать не только в цикле while, но и в циклах for и do-while. Например, если нам нужно из диапазона целых чисел [1; 100] отобразить только те, что кратны 3 и 5 одновременно, то это можно сделать следующим образом:

#include <stdio.h>
 
int main(void)
{
         for(int i = 1; i <= 100; ++i) {
                   if(i % 3 != 0 || i % 5 != 0)
                            continue;
 
                   printf("%d ", i);
         }
 
         return 0;
}

Это учебный пример. В этой задаче использование оператора continue избыточно и проще было бы вызывать функцию printf() по обратному условию. Я лишь показываю, что операторы continue и break допустимо применять с любыми операторами циклов.

Оператор goto

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

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

Итак, предположим, что мы вычисляем двойную сумму вида:

и хотим прервать вычисления как только слагаемое i-j будет больше нуля. Такую программу можно реализовать следующим образом:

#include <stdio.h>
 
int main(void)
{
         int s = 0;
         for(int i = 1; i <= 10; ++i)
                   for(int j = 7; j >= 5; --j) {
                            if(i-j > 0)
                                      goto exit_sum;
                            s += i - j;
                   }
 
         exit_sum: printf("s = %d\n", s);
 
         return 0;
}

Смотрите, здесь два цикла, один вложен в другой. В теле второго цикла выполняется проверка на положительность значения i-j. Если это так, то по условию задания нам нужно прервать работу обоих циклов и завершить подсчет суммы. Как раз для этого удобно воспользоваться оператором goto и по метке exit_sum (имя мы придумываем сами) переходим к оператору printf(), стоящему после этих циклов. В результате такого перехода работа циклов будет завершена.

Возможно, некоторые из вас сейчас в недоумении смотрят на эту программу и задаются вопросом: а почему бы здесь не использовать оператор break, он же как раз и позволяет прервать цикл? Все верно, цикл (один цикл) он может прервать и если записать программу в виде:

         for(int i = 1; i <= 10; ++i)
                   for(int j = 7; j >= 5; --j) {
                            if(i-j > 0)
                                      break;
                            s += i - j;
                   }

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

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

Чтобы гарантированно избежать этих недостатков при построении программ, стоит совсем отказаться от применения оператора goto. Исключение может составлять необходимость прерывания сразу нескольких вложенных циклов. Хотя и здесь мы могли бы обойтись без этого оператора. Часто логику вложенных циклов реализуют в отдельных функциях. И как только появляется необходимость прервать их работу, завершают функцию оператором return. Это более частый вариант по сравнению с оператором goto. Именно так я рекомендую поступать при проектировании своих программ.

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

Видео по теме