Страницы

Поиск по вопросам

среда, 18 декабря 2019 г.

Нетипичный выход из switch и for: выход из нескольких вложенных блоков и циклов

#cpp #c


Как, находясь в switch(b), выйти сразу из switch(a) (switch(b) находится внутри case
в а), при этом не выходя из функции с, в которой они находятся?

void c()
{
    switch (a)
    {
        case 1:
            switch (b)
            {
                case 1:
                    ???
                    break;
            }
            break;
    }
    return;
}


И ещё вопрос - как, находясь в цикле, который находится в switch, выйти сразу из
switch, не выходя из функции (и наоборот - если switch находится в for)?

void c()
{
    switch (a)
    {
        case 1:
            for (;;)
            {
                ???
            }
            break;
    }
    return;
}


goto не принимается!`
    


Ответы

Ответ 1



Лирическое отступление Позиция "goto не принимается" берётся из непонимания проблемы goto, и почему Дейкстра написал свой исторический документ, а Вирт изуродовал goto в Паскале. Исторически противостояние с goto позникло на фоне перехода на структурное программирование. Проблема goto в том, что при использовании безусловных переходов без ограничений код превращается в неподдерживаемую лапшу, в которой невозможно понять, какой кусок выполняется после какого, и при каких условиях. В старых языках и при старых компиляторах с помощью goto можно было совершать невообразимые вещи: обходить инициализацию переменных, врываться внутрь циклов и вообще произвольных функций. Легко понять, что подобное использование goto встречало неприятие. Структурное программирование решало эту проблему, превращая типичные использования goto в условные переходы и циклы. Все эти if, else, while, for, do и прочие, которые теперь есть во всех языках — не более, чем паттерны использования goto (если быть точнее, конструкции if (a) goto b) с синтаксисом на уровне языка. Проблема в том, что структурное программирование эффективно решает не все реально возникающие проблемы. Осталось несколько красивых и эффективных паттернов, которые так и остаются во власти goto: быстрый выход из нескольких циклов, освобождение ресурсов при отстутвии RAII, переход по состояниям автомата и некоторые другие. В этом случае уже использование структурного программирования превращает код в лапшу, а код с goto остаётся чистым и понятным, если хорошо понимать эти паттерны. Кроме того, проблема использования goto для входа в произвольный кусок кода давно неактуальна: современные компиляторы просто не позволят вам обойти инициализацию переменной или проделать какую-нибудь подобную шалость. Структурное программирование Некоторые языки предлагают дополнительные конструкции: именованные блоки и параметризация break именами блоков (outer: for { for { break outer } }), выход из произвольного числа блоков заданием числового параметра (for { for { break 2 } }) и прочие костыли. В C++ этого всего нет. У вас остаётся только традиционный костыль выхода из вложенных циклов/блоков — вынесение в отдельную функцию и использование return. Только учтите, что использование return где-то помимо конца функции тоже воспринимается как ересь ярыми адептами структурного программирования. Если вы такой ярый адепт, то у вас один путь: вводить дополнительные переменные. По сути, вся идея противостояния goto зиждется на том факте, что любой код с goto можно преобразовать к коду без goto путём введения переменных. void c() { bool exitCase1 = false; switch (a) { case 1: switch (b) { case 1: exitCase1 = true; break; } if (exitCase1) break; } return; } Аналогично со вложенными циклами: везде добавляйте переменные и дополняйте код условиями выхода: for (int i = 0; i < 10 && !exitFor; i++) for (int j = 0; j < 10 && !exitFor; j++) .... Учтите, что подобный код с морем if и переменных для значительной части программистов будет выглядеть заметно сложнее, чем код с goto. Кроме того, подобный код менее эффективен: безусловный переход с помощью goto, очевидно, работает быстрее, чем многочисленные проверки на каждом уровне вложенности, особенно в случае циклов. И упасите вас боги воспользоваться исключениями для выхода из цикла — это надругательство над языком, за которое вас посадят на кол и адепты, и противники goto. Исключения должны использоваться для обработки исключительных ситуаций, а не для управления потоком выполнения программы. Нормальное решение Просто используйте goto. Уже больше 40 лет прошло после структурной революции, пора расстаться со старыми слепыми религиозными взглядами и принять реальность такой, какая она есть. А в этой реальности до сих пор остаются задачи, которые эффективно решаются с помощью goto. Профессионалы должны знать паттерны правильного использования goto, а не прятать голову в песок и писать лапшекод из-за отживших свой век взглядов, которые насадили предки. Впрочем, всегда следует стараться пользоваться возможностями языка, которые приходят на замену goto, если таковые есть. Например, если в языке есть выход из нескольких блоков с помощью break, то goto здесь не нужен. Если в языке есть RAII (конструкторы-деструкторы в C++) или конструкции для освобождения ресурсов (using в C#), то goto для освобождения ресурсов не нужен.

Ответ 2



Как вариант - при помощи исключений, хотя с точки зрения пуристов от C++ это плохой вариант. Второй способ - заведение булевых флагов с их установкой и проверкой. Именованные break в C++ пока не завели...

Комментариев нет:

Отправить комментарий