7.6 – Операторы goto
Следующий тип инструкций управления порядком выполнения программы, который мы рассмотрим, – это безусловный переход. Безусловный переход заставляет выполнение перемещаться в другое место в коде. Термин «безусловный» означает, что переход происходит всегда (в отличие от оператора if
или оператора switch
, где переход происходит только на основе результата условного выражения).
В C++ безусловные переходы реализуются с помощью инструкции goto
, а точка перехода определяется с помощью метки инструкции. Ниже приведен пример инструкции goto
и метки инструкции:
#include <iostream>
#include <cmath> // для функции sqrt()
int main()
{
double x{};
tryAgain: // это метка инструкции
std::cout << "Enter a non-negative number: ";
std::cin >> x;
if (x < 0.0)
goto tryAgain; // это инструкция goto
std::cout << "The square root of " << x << " is " << std::sqrt(x) << '\n';
return 0;
}
В этой программе пользователя просят ввести неотрицательное число. Однако если введено отрицательное число, программа использует инструкцию goto
для возврата к метке tryAgain
. Затем пользователя снова просят ввести новое число. Таким образом, мы можем постоянно просить пользователя ввести данные, пока он или она не введет что-то допустимое.
Вот пример вывода при запуске этой программы:
Enter a non-negative number: -4
Enter a non-negative number: 4
The square root of 4 is 2
Метки инструкций имеют область видимости
В главе, посвященной области видимости объектов (глава 6), мы рассмотрели три вида области видимости: локальную область видимости (область видимости бллока), область видимости файла и глобальную область видимости. Метки инструкций используют четвертый вид области видимости: область видимости функции, что означает, что метка видна во всей функции даже до точки ее объявления. Инструкция goto
и соответствующая ей метка инструкции должны находиться в одной функции.
Хотя в приведенном выше примере показана инструкция goto
, которая выполняет переход назад (к предыдущей точке в функции), инструкции goto
могут перемещаться и вперед:
#include <iostream>
void printCats(bool skip)
{
if (skip)
goto end; // переход вперед; метка инструкции 'end' видна здесь,
// так как имеет область видимости функции
std::cout << "cats";
end:
; // метки инструкций должны быть связаны с инструкциями
}
int main()
{
printCats(true); // перепрыгнуть через инструкцию печати и ничего не печатать
printCats(false); // напечатать"cats"
return 0;
}
Эта программа напечатает:
cats
Помимо перехода вперед, в приведенной выше программе стоит упомянуть еще несколько интересных вещей.
Во-первых, обратите внимание, что метки инструкций должны быть связаны с инструкциями (отсюда и их название: они помечают инструкцию). Поскольку в конце функции не было инструкции, нам пришлось использовать пустую инструкцию, чтобы у нас было что пометить. Во-вторых, мы смогли перейти к инструкции, помеченной как end
, хотя мы даже еще не объявили end
, это потому, что метки инструкций имеют область видимости функции. Предварительное объявление меток инструкций не требуется. В-третьих, стоит прямо упомянуть, что приведенная выше программа – плохой пример: было бы лучше использовать оператор if
, чтобы пропустить инструкцию печати, а не оператор goto
, чтобы перепрыгнуть через нее.
У переходов есть два основных ограничения: вы можете выполнять переходы вперед или назад только в рамках одной функции (вы не можете перепрыгивать из одной функции в другую), и если вы выполняете переход вперед, вы не можете перескочить вперед через инициализацию любой переменной, которая находится в области видимости в месте перехода. Например:
int main()
{
goto skip; // ошибка: этот переход недопустим, потому что...
int x { 5 }; // эта инициализированная переменная все еще находится
// в области видимости в метке инструкции 'skip'
skip:
x += 3; // как это должно было бы вычисляться,
// если бы x не была инициализирована?
return 0;
}
Обратите внимание, что вы можете выполнить переход назад через инициализацию переменной, и при выполнении инициализации переменная будет повторно инициализирована.
Избегайте использования goto
Использование goto
в C++ (а также в других современных высокоуровневых языках) не допускается. Эдсгер В. Дейкстра, известный ученый-информатик, изложил аргументы в пользу отказа от goto
в известной, но трудной для чтения статье под названием «Go To Statement Considered Harmful». Основная проблема с goto
заключается в том, что он позволяет программисту произвольно перемещаться по коду. Это создает то, что не очень ласково называют спагетти-кодом. Спагетти-код – это код, порядок выполнения которого напоминает тарелку спагетти (все запутанные и скрученные), что чрезвычайно затрудняет отслеживание логики такого кода.
Как с юмором говорит Дейкстра, «качество программистов – это функция, обратно пропорциональная плотности инструкций переходов в создаваемых ими программах».
Практически любой код, написанный с помощью операторов goto
, можно более четко написать с использованием других конструкций C++, таких как операторы if
и циклы. Одно примечательное исключение – это когда вам нужно выйти из вложенного цикла, но не из всей функции – в таком случае переход сразу за пределы циклов, вероятно, является самым чистым решением.
Лучшая практика
Избегайте операторов goto
(если альтернативы значительно не ухудшают читаемость кода).