Как вырваться из петли внутри переключателя?

114

Я пишу код, который выглядит так:

while(true) {
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        break; // **HERE, I want to break out of the loop itself**
    }
}

Есть ли прямой способ сделать это?

Я знаю, что могу использовать флаг и выйти из цикла, поставив условный разрыв сразу после переключателя. Я просто хочу знать, есть ли в C ++ для этого уже какая-то конструкция.

Джархатх
источник
21
Зачем нужен условный перерыв после переключения? Просто измените значение while с while (true) на while (flag) ...
Тал Прессман,
7
@Dave_Jarvis Я предполагаю, что это упрощенная версия, которую он вставил здесь, чтобы проиллюстрировать, что он пытался сделать.
Alterlife
6
Если вы один из тех программистов, которые создают функции длиной в несколько страниц, вы найдете gotoпривлекательный, а иногда и единственный чистый выход. Если вы склонны организовывать свой код в небольшие функции, состоящие всего из нескольких строк и выполняющие одну единственную задачу каждая, вы никогда не столкнетесь с этой проблемой. (Кстати, ваш код тоже будет легче читать.)
SBI,
13
Если хочется получить совет бросить курить, когда все, что вам нужно знать, это как добраться до ближайшей станции метро.
Михаил Крелин - хакер,
7
@hacker: Ну, если вы не видите перед собой станцию ​​метро из-за большого количества дыма, этот совет может быть не так уж и плох. :)
sbi

Ответы:

50

посылка

Следующий код следует считать плохим тоном, независимо от языка или желаемой функциональности:

while( true ) {
}

Подтверждающие аргументы

while( true )Петля плохая форма потому , что он:

  • Разрывает подразумеваемый контракт цикла while.
    • Объявление цикла while должно явно указывать единственное условие выхода.
  • Подразумевается, что он зацикливается навсегда.
    • Код внутри цикла должен быть прочитан, чтобы понять завершающее предложение.
    • Циклы, которые повторяются бесконечно, не позволяют пользователю завершить программу изнутри программы.
  • Неэффективно.
    • Есть несколько условий завершения цикла, включая проверку на «истину».
  • Склонен к ошибкам.
    • Трудно определить, где разместить код, который всегда будет выполняться для каждой итерации.
  • Приводит к излишне сложному коду.
  • Автоматический анализ исходного кода.
    • Для поиска ошибок, анализа сложности программы, проверок безопасности или автоматического получения любого другого поведения исходного кода без выполнения кода указание начальных условий нарушения позволяет алгоритмам определять полезные инварианты, тем самым улучшая показатели автоматического анализа исходного кода.
  • Бесконечные петли.
    • Если все всегда используют while(true)циклы for, которые не являются бесконечными, мы теряем способность кратко общаться, когда циклы фактически не имеют условия завершения. (Возможно, это уже произошло, поэтому вопрос спорный.)

Альтернатива "Перейти к"

Следующий код лучше:

while( isValidState() ) {
  execute();
}

bool isValidState() {
  return msg->state != DONE;
}

Преимущества

Нет флага. Нет goto. Без исключения. Легко поменять. Легко читать. Легко исправить. Дополнительно код:

  1. Изолирует информацию о рабочей нагрузке цикла от самого цикла.
  2. Позволяет кому-то поддерживать код для простого расширения функциональности.
  3. Позволяет назначать несколько условий завершения в одном месте.
  4. Отделяет завершающее предложение от кода для выполнения.
  5. Безопаснее для АЭС. ;-)

Второй момент важен. Не зная, как работает код, если кто-то попросит меня сделать так, чтобы основной цикл позволял другим потокам (или процессам) иметь некоторое процессорное время, на ум приходят два решения:

Опция 1

С готовностью вставьте паузу:

while( isValidState() ) {
  execute();
  sleep();
}

Вариант # 2

Выполнить переопределение:

void execute() {
  super->execute();
  sleep();
}

Этот код проще (поэтому его легче читать), чем цикл со встроенным switch. isValidStateМетод должен определить , только если цикл должен быть продолжен. Рабочая лошадка метода должна быть абстрагирована в executeметод, который позволяет подклассам переопределять поведение по умолчанию (сложная задача с использованием встроенного switchи goto).

Пример Python

Сравните следующий ответ (на вопрос Python), опубликованный на StackOverflow:

  1. Цикл навсегда.
  2. Попросите пользователя ввести свой выбор.
  3. Если пользователь вводит «перезапуск», продолжайте цикл бесконечно.
  4. В противном случае перестаньте зацикливаться навсегда.
  5. Конец.
Код
while True: 
    choice = raw_input('What do you want? ')

    if choice == 'restart':
        continue
    else:
        break

print 'Break!' 

Против:

  1. Инициализировать выбор пользователя.
  2. Цикл, пока выбор пользователя - слово «перезапуск».
  3. Попросите пользователя ввести свой выбор.
  4. Конец.
Код
choice = 'restart';

while choice == 'restart': 
    choice = raw_input('What do you want? ')

print 'Break!'

Здесь while Trueполучается вводящий в заблуждение и чрезмерно сложный код.

Дэйв Джарвис
источник
45
Идея о том, что while(true)должно быть вредным, кажется мне странной. Есть циклы, которые проверяют перед выполнением, есть те, которые проверяют после, и есть те, которые проверяют посередине. Поскольку у последнего нет синтаксической конструкции в C и C ++, вам придется использовать while(true)или for(;;). Если вы считаете это неправильным, значит, вы еще недостаточно продумали различные типы циклов.
sbi,
34
Причины, по которым я не согласен: while (true) и for (;;;) не сбивают с толку (в отличие от do {} while (true)), потому что они прямо заявляют, что делают. На самом деле легче искать операторы «break», чем разбирать произвольное условие цикла и искать, где оно установлено, и не труднее доказать правильность. Спор о том, куда поместить код, так же трудноразрешим в присутствии оператора continue, и не намного лучше с операторами if. Аргумент эффективности просто глуп.
Дэвид Торнли,
23
Всякий раз, когда вы видите «while (true)», вы можете думать о цикле с внутренними условиями завершения, которые не сложнее, чем произвольные условия завершения. Проще говоря, цикл while (foo), где foo - это логическая переменная, установленная произвольно, не лучше, чем while (true) с перерывами. В обоих случаях вам придется просматривать код. Высказывание глупой причины (а микроэффективность - глупый аргумент), кстати, не поможет вашему делу.
Дэвид Торнли,
5
@ Дэйв: Я объяснил причину: это единственный способ получить цикл, который проверяет условие посередине. Классический пример: while(true) {string line; std::getline(is,line); if(!is) break; lines.push_back(line);}конечно, я мог бы преобразовать это в предварительно std::getlineобусловленный цикл (используя как условие цикла), но это само по себе имеет недостатки ( lineпеременная должна быть вне области действия цикла). И если бы я это сделал, мне пришлось бы спросить: а почему у нас вообще три петли? Мы всегда могли преобразовать все в заранее подготовленный цикл. Используйте то, что подходит лучше всего. Если while(true)подходит, то используйте.
sbi
3
В знак любезности к людям, читающим этот ответ, я бы не стал комментировать качество товара, из-за которого был задан вопрос, и придерживался самого ответа. Вопрос заключается в вариации следующего, что, как я считаю, является особенностью многих языков: у вас есть цикл, вложенный в другой цикл. Вы хотите выйти из внешнего цикла из внутреннего. Как это сделать?
Alex Leibowitz
172

Вы можете использовать goto.

while ( ... ) {
   switch( ... ) {
     case ...:
         goto exit_loop;

   }
}
exit_loop: ;
Мехрдад Афшари
источник
12
Только не сходи с ума с этим ключевым словом.
Fragsworth
45
Забавно, что меня отвергают, потому что людям это не нравится goto. OP четко упоминается без использования флагов . Можете ли вы предложить лучший способ, учитывая ограничение ОП? :)
Mehrdad Afshari
18
проголосовали за t, чтобы компенсировать бездумных ненавистников goto. Думаю, Мердад знал, что потеряет пару очков за предложение разумного решения.
Михаил Крелин - хакер,
6
+1, нет лучшего способа imho, даже с учетом ограничений OP. Флаги уродливы, разбивают логику по всему циклу, и поэтому их труднее понять.
Йоханнес Шауб - лит,
11
в конце концов, без конструкции goto все языки терпят неудачу. языки высокого уровня запутывают это, но если вы достаточно внимательно посмотрите под капотом, вы обнаружите, что каждый цикл или условие сводится к условным и безусловным переходам. мы обманываем себя, используя ключевые слова for, while, until, switch, if, но в конечном итоге все они так или иначе используют GOTO (или JMP). вы можете обманывать себя, изобретая всевозможные хитрые способы скрыть это, или вы можете просто быть прагматично честным и использовать goto там, где это необходимо, и экономно.
несинхронизировано
58

Альтернативное решение - использовать ключевое слово continueв сочетании с break, например:

for (;;) {
    switch(msg->state) {
    case MSGTYPE
        // code
        continue; // continue with loop
    case DONE:
        break;
    }
    break;
}

Используйте continueоператор для завершения каждой метки case, где вы хотите, чтобы цикл продолжался, и используйте breakоператор для завершения меток case, которые должны завершать цикл.

Конечно, это решение работает только в том случае, если нет дополнительного кода для выполнения после оператора switch.

сакра
источник
9
Хотя это действительно очень элегантно, у него есть тот недостаток, что большинству разработчиков сначала нужно взглянуть на него в течение минуты, чтобы понять, как / работает ли это. :(
sbi,
8
Последнее предложение - то, что делает его не таким уж хорошим. :(В остальном очень чистая реализация.
nawfal
Если подумать, никакой компьютерный код не годится. Нас только учат понимать это. Например, xml выглядел как мусор, пока я его не изучил. Теперь он выглядит менее мусорным. for (int x = 0; x <10; ++ x, moveCursor (x, y)) фактически вызвало логическую ошибку в моей программе. Я забыл, что условие обновления произошло в конце.
23

Изящный способ сделать это - поместить это в функцию:

int yourfunc() {

    while(true) {

        switch(msg->state) {
        case MSGTYPE: // ... 
            break;
        // ... more stuff ...
        case DONE:
            return; 
        }

    }
}

Необязательно (но «плохие практики»): как уже предлагалось, вы можете использовать goto или выбросить исключение внутри переключателя.

Альтерлайф
источник
4
Исключение следует использовать для генерации исключения, а не для хорошо известного поведения функции.
Клемент Херреман,
Я согласен с загробной жизнью: поместите это в функцию.
далле
14
В этом случае создание исключения - действительно зло. Это означает: «Я хочу использовать goto, но я где-то читал, что мне не следует их использовать, поэтому я пойду с уловкой и сделаю вид, что выгляжу умным».
Gorpik
Я согласен с тем, что выброс исключения или использование goto - ужасная идея, но они являются рабочими вариантами и были указаны как таковые в моем ответе.
Alterlife
2
Создание исключения заменяет COMEFROM на GOTO. Не делай этого. Goto работает намного лучше.
Дэвид Торнли,
14

Насколько я знаю, в С ++ нет «двойного разрыва» или аналогичной конструкции. Ближайшим из них был бы goto- который, хотя и имеет плохую коннотацию к своему названию, существует в языке по какой-то причине - пока он используется осторожно и экономно, это жизнеспособный вариант.

Янтарь
источник
9

Вы можете поместить свой переключатель в отдельную функцию, например:

bool myswitchfunction()
{
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        return false; // **HERE, I want to break out of the loop itself**
    }
    return true;
}

while(myswitchfunction())
    ;
Адам Пирс
источник
7

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

try {
  while ( ... ) {
    switch( ... ) {
      case ...:
        throw 777; // I'm afraid of goto
     }
  }
}
catch ( int )
{
}

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

Но я думаю, что использование goto- единственный вариант здесь из-за строки while(true). Вам следует подумать о рефакторинге вашего цикла. Я бы предположил следующее решение:

bool end_loop = false;
while ( !end_loop ) {
    switch( msg->state ) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        end_loop = true; break;
    }
}

Или даже следующее:

while ( msg->state != DONE ) {
    switch( msg->state ) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
}
Кирилл Васильевич Лядвинский
источник
1
Да, но мне даже меньше нравятся исключения ;-)
quamrana
3
Использование исключения для имитации goto, конечно, хуже, чем фактическое использование goto! Вероятно, он также будет значительно медленнее.
Джон Картер,
2
@Downvoter: это потому, что я заявляю, что вам не следует использовать исключения, или потому, что я предлагаю рефакторинг?
Кирилл В. Лядвинский
1
Не тот, кто проголосовал против, но ... Ваш ответ неясен, предлагаете вы или осуждаете идею использования исключения для выхода из цикла. Может быть, первое предложение должно быть: «Даже если вам не нравится goto, не используйте исключение для выхода из цикла:»?
Джонатан Леффлер,
1
@Jonathan Leffler, Спасибо, вы показали образец ценного комментария. обновил ответ с учетом ваших комментариев.
Кирилл В. Лядвинский
5

В этом случае нет конструкции C ++ для выхода из цикла.

Либо используйте флаг, чтобы прервать цикл, либо (при необходимости) извлеките код в функцию и используйте return.

Sharptooth
источник
3

Нет, в C ++ нет конструкции для этого, поскольку ключевое слово break уже зарезервировано для выхода из блока переключения. В качестве альтернативы может быть достаточно do..time () с флагом выхода.

do { 
    switch(option){
        case 1: ..; break;
        ...
        case n: .. ;break;
        default: flag = false; break;
    }
} while(flag);
Прашант Шрирам
источник
2

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

Мартин Йорк
источник
2

Почему бы просто не исправить условие в вашем цикле while, чтобы проблема исчезла?

while(msg->state != DONE)
{
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        // We can't get here, but for completeness we list it.
        break; // **HERE, I want to break out of the loop itself**
    }
}
Марк Б
источник
1

Меня поражает, насколько это просто, учитывая глубину объяснений ... Вот все, что вам нужно ...

bool imLoopin = true;

while(imLoopin) {

    switch(msg->state) {

        case MSGTYPE: // ... 
            break;

        // ... more stuff ...

        case DONE:
            imLoopin = false;
            break;

    }

}

РЖУНИМАГУ!! В самом деле! Это все, что вам нужно! Одна дополнительная переменная!

Марк А. Донохью
источник
1

Думаю;

while(msg->state != mExit) 
{
    switch(msg->state) 
    {
      case MSGTYPE: // ...
         break;
      case DONE:
      //  .. 
      //  ..
      msg->state =mExit;
      break;
    }
}
if (msg->state ==mExit)
     msg->state =DONE;
Эркан
источник
0

Самый простой способ сделать это - поставить простой IF перед выполнением SWITCH, и этот IF проверяет ваше условие для выхода из цикла .......... настолько просто, насколько это возможно

SpiriAd
источник
2
Гм ... вы могли бы быть еще проще, поставив это условие в аргумент while. Я имею в виду, что он там для этого! :)
Марк А. Донохо
0

breakКлючевое слово в C ++ только завершает наиболее вложенные ограждающие итерации или switchутверждение. Таким образом, вы не можете выйти из while (true)цикла непосредственно внутри switchоператора; однако вы можете использовать следующий код, который, я думаю, является отличным шаблоном для этого типа проблемы:

for (; msg->state != DONE; msg = next_message()) {
    switch (msg->state) {
    case MSGTYPE:
        //...
        break;

    //...
    }
}

Если вам нужно было что-то сделать при msg->stateравенстве DONE(например, запустить процедуру очистки), поместите этот код сразу после forцикла; т.е. если у вас есть:

while (true) {
    switch (msg->state) {
    case MSGTYPE:
        //... 
        break;

    //...

    case DONE:
        do_cleanup();
        break;
    }

    if (msg->state == DONE)
        break;

    msg = next_message();
}

Тогда используйте вместо этого:

for (; msg->state != DONE; msg = next_message()) {
    switch (msg->state) {
    case MSGTYPE:
        //...
        break;

    //...
    }
}

assert(msg->state == DONE);
do_cleanup();
Даниэль Треббиен
источник
0
while(MyCondition) {
switch(msg->state) {
case MSGTYPE: // ... 
    break;
// ... more stuff ...
case DONE:
   MyCondition=false; // just add this code and you will be out of loop.
    break; // **HERE, you want to break out of the loop itself**
}
}
Wajeed-MSFT
источник
0

У меня такая же проблема, и я решил ее с помощью флага.

bool flag = false;
while(true) {
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        flag = true; // **HERE, I want to break out of the loop itself**
    }
    if(flag) break;
}
Рам Сутар
источник
-2

Если я хорошо помню синтаксис C ++, вы можете добавить метку к breakоператорам, как для goto. Итак, то, что вы хотите, было бы легко написать:

while(true) {
    switch(msg->state) {
    case MSGTYPE: // ...
        break;
    // ... more stuff ...
    case DONE:
        break outofloop; // **HERE, I want to break out of the loop itself**
    }
}

outofloop:
// rest of your code here
Андрей Вайна II
источник
1
К сожалению, ваша память неисправна. Это была бы хорошая функция в тех редких случаях, когда она была бы полезной. (Я видел предложения по количеству уровней, с которых нужно выйти, но мне они кажутся ошибками, ожидающими своего появления.)
Дэвид Торнли,
Значит, это была Java. Спасибо за ответы. И, конечно же, ты прав и в остальном.
Андрей Вайна II
-3
  while(true)
  {
    switch(x)
    {
     case 1:
     {
      break;
     }
    break;
   case 2:
    //some code here
   break;
  default:
  //some code here
  }
}
Chevaughn
источник
1
Все разрывы в этом примере кода выходят за рамки оператора switch.
Дэн Халм