Как вырваться из петли изнутри выключатель? - PullRequest
100 голосов
/ 14 сентября 2009

Я пишу код, который выглядит следующим образом:

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

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

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

Ответы [ 19 ]

146 голосов
/ 14 сентября 2009

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

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

   }
}
exit_loop: ;
52 голосов
/ 14 сентября 2009

Предпосылка

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

while( true ) {
}

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

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

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

Альтернатива "Go To"

Следующий код является лучшей формой:

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!' 

Versus:

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

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

print 'Break!'

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

50 голосов
/ 14 сентября 2009

Альтернативным решением является использование ключевого слова continue в сочетании с break, т. Е .:

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

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

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

20 голосов
/ 14 сентября 2009

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

int yourfunc() {

    while(true) {

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

    }
}

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

13 голосов
/ 14 сентября 2009

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

8 голосов
/ 14 сентября 2009

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

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 голосов
/ 14 сентября 2009

Даже если вам не нравится 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 ...
}
5 голосов
/ 14 сентября 2009

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

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

2 голосов
/ 14 сентября 2009

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

2 голосов
/ 07 апреля 2017

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

do { 
    switch(option){
        case 1: ..; break;
        ...
        case n: .. ;break;
        default: flag = false; break;
    }
} while(flag);
...