Как, находясь в 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 не принимается!`
Ответ
Лирическое отступление
Позиция "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 для освобождения ресурсов не нужен.
Комментариев нет:
Отправить комментарий