7.6 – Операторы goto

Добавлено 28 мая 2021 в 21:45

Следующий тип инструкций управления порядком выполнения программы, который мы рассмотрим, – это безусловный переход. Безусловный переход заставляет выполнение перемещаться в другое место в коде. Термин «безусловный» означает, что переход происходит всегда (в отличие от оператора 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 (если альтернативы значительно не ухудшают читаемость кода).

Теги

C++ / CppgotoLearnCppДля начинающихОбучениеПрограммированиеСпагетти-код

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.