Як вирватися з петлі зсередини вимикача?


113

Я пишу код, який виглядає приблизно так:

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

Чи є прямий спосіб це зробити?

Я знаю, що можу використовувати прапор і вирватися з циклу, поставивши умовний перерву відразу після вимикача. Я просто хочу знати, чи є у C ++ якась конструкція для цього.


20
Навіщо потрібна умовна перерва після вимикача? Просто змініть свій час на час (правда) на час (прапор) ...
Тал Прессман,

7
@Dave_Jarvis Я припускаю, що це спрощена версія, яку він вклав сюди, щоб проілюструвати, що він намагався зробити.
Альтерлайф

6
Якщо ви один з цих програмістів, які виробляють функції, що мають кілька сторінок, ви знайдете gotoпривабливий і, часом, єдиний чистий вихід. Якщо ви схильні впорядковувати свій код на невеликих функціях, довжиною яких є лише кілька рядків, і виконуються по одній речі кожен, ви ніколи не зіткнетеся з цією проблемою. (До речі, і ваш код буде простішим для читання.)
sbi

13
Якщо вам хочеться отримати пораду кинути палити, коли все, що ви хочете знати, - як дістатися до найближчої станції метро.
Майкл Крелін - хакер

7
@hacker: Ну, якщо ви не можете побачити станцію метро перед вами через весь дим, ця порада може бути не такою поганою. :)
sbi

Відповіді:


49

Приміщення

Наступний код слід вважати поганою формою, незалежно від мови та бажаної функціональності:

while( true ) {
}

Підтверджуючі аргументи

while( true )Петля погана форма тому , що він:

  • Розриває мається на увазі контракт циклу на час.
    • У оголошенні циклу while слід чітко вказати єдину умову виходу.
  • Мається на увазі, що це петля назавжди.
    • Код у циклі повинен бути прочитаний, щоб зрозуміти закінчення.
    • Цикли, які повторюються назавжди, заважають користувачеві закінчувати програму всередині програми.
  • Неефективна.
    • Існує кілька умов припинення циклу, включаючи перевірку на "справжню".
  • Схильний до помилок.
    • Не можна легко визначити, куди слід поставити код, який завжди буде виконуватися для кожної ітерації.
  • Приводить до зайвого складного коду.
  • Автоматичний аналіз вихідного коду.
    • Для пошуку помилок, аналізу складності програми, перевірок безпеки або автоматичного отримання будь-якої іншої поведінки вихідного коду без виконання коду, уточнення початкових умов порушення дозволяє алгоритмам визначати корисні інваріанти, тим самим покращуючи показники автоматичного аналізу вихідного коду.
  • Нескінченні петлі.
    • Якщо всі завжди використовують while(true)для циклів, які не є нескінченними, ми втрачаємо здатність стисло спілкуватися, коли петлі насправді не мають умови завершення. (Можливо, це вже сталося, тому справа в суперечці.)

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

Наступний код є кращою формою:

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
Причини, чому я не згоден: в той час, як (правда) і для (;;;) не плутають (на відміну від {} в той час, як (правда)), тому що вони заявляють, що вони роблять прямо вперед. Насправді простіше шукати твердження "розрив", ніж розбирати умову умовного циклу і шукати, де вона встановлена, і не важче довести правильність. Аргумент про те, куди слід поставити код, настільки ж непереборний за наявності оператора «продовження», і не набагато краще, якщо заяви. Аргумент ефективності позитивно нерозумний.
Девід Торнлі

23
Щоразу, коли ви бачите "while (true)", ви можете думати про цикл, який має внутрішні умови припинення, які не складніше, ніж довільні кінцеві умови. Просто, цикл "while (foo)", де foo булева змінна, задана довільним чином, не кращий, ніж "while (true)" з розривами. Ви повинні переглянути код в обох випадках. Викладення дурної причини (а мікроефективність - дурний аргумент), до речі, не допомагає вашому випадку.
Девід Торнлі

5
@Dave: Я пояснив причину: це єдиний спосіб отримати цикл, який перевіряє стан посередині. Класичний приклад: while(true) {string line; std::getline(is,line); if(!is) break; lines.push_back(line);}Звичайно, я міг би перетворити це на попередньо std::getlineобумовлений цикл (використовуючи як умову циклу), але це має свої недоліки самостійно ( lineповинно бути змінною поза межами циклу). І якби я це зробив, я мав би запитати: навіщо нам взагалі три петлі? Ми завжди могли перетворити все на попередньо обумовлену петлю. Використовуйте те, що найкраще підходить. Якщо while(true)підходить, то використовуйте.
sbi

3
Як люб’язно людям, які читають цю відповідь, я б уникав коментувати якість товару, який спричинив питання, і дотримуватись самого відповіді на запитання. Питання полягає в варіації наступного, що, на мою думку, є особливістю багатьох мов: у вас є петля, вкладена всередині іншого циклу. Ви хочете вирватись із зовнішньої петлі із внутрішньої петлі. Як це робиться?
Алекс Лейбовіц

172

Можна використовувати goto.

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

   }
}
exit_loop: ;

12
Просто не дивіться за цим ключовим словом.
Fragsworth

45
Смішно, що я неприхильний, тому що людям це не подобається goto. ОП чітко згадується без використання прапорів . Чи можете ви запропонувати кращий спосіб, враховуючи обмеження ОП? :)
Мехрдад Афшарі

18
щосили намагаються компенсувати бездумних ненависників гото. Я думаю, Мехрдад знав, що він втратить пару балів за те, щоб запропонувати розумне рішення тут.
Майкл Крелін - хакер

6
+1, немає кращого способу, навіть якщо враховувати обмеження ОП. Прапори некрасиві, розбивають логіку по всій петлі і, таким чином, важче зрозуміти.
Йоханнес Шауб - ліб

11
в кінці дня без конструкції goto всі мови виходять з ладу. Мови високого рівня замовчують це, але якщо ви досить жорстко подивитесь під кришку, ви знайдете кожну петлю, або умова зводиться до умовних і безумовних гілок. ми обманюємо себе, використовуючи для, в той час, як, поки, перемикаємо, якщо ключові слова, але зрештою всі вони використовують так чи інакше GOTO (або JMP). ви можете вводити себе в оману, вигадуючи всілякі розумні способи, щоб приховати це, або можете бути просто прагматично чесними і використовувати goto, де це доречно, і щадно.
несинхронізований

57

Альтернативним рішенням є використання ключового слова continueв поєднанні з break, тобто:

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

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

Звичайно, це рішення працює лише в тому випадку, якщо після оператора переключення немає додаткового коду для виконання.


9
Хоча це справді дуже елегантно, але він недоліком є ​​те, що більшості розробників спочатку доводиться дивитися на нього хвилину, щоб зрозуміти, як / чи це працює. :(
sbi

8
Останнє речення - це робить це не таким великим :(.
nawfal

Коли ви думаєте про це, жоден комп’ютерний код не корисний. Ми лише навчені розуміти це. Наприклад, xml виглядав як сміття, поки я не дізнався його. Зараз це менше виглядає сміття. for (int x = 0; x <10; ++ x, moveCursor (x, y)) фактично спричинив логічну помилку в моїй програмі. Я забув, що стан оновлення стався наприкінці.

22

Акуратним способом зробити це було б ввести це у функцію:

int yourfunc() {

    while(true) {

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

    }
}

Необов’язково (але "погані практики"): як уже було запропоновано, ви можете використовувати goto або кинути виняток всередині комутатора.


4
Виняток слід використовувати для того, щоб викинути виняток, а не добре знати поведінку функції.
Клемент Герреман

Я згоден із Загробним життям: Покладіть його на функцію.
dalle

14
Кидати виняток - справді зло в цьому випадку. Це означає "я хочу використовувати гото, але я десь прочитав, що не повинен їх використовувати, тому піду з підмовою і прикинусь, що виглядаю розумно".
Горпик

Я погоджуюсь, що кидати виняток або використовувати goto - це жахлива ідея, але вони працюють, і вони є такими, що були вказані у моїй відповіді.
Alterlife

2
Якщо викинути виняток, це замінити КОМЕРОМ на GOTO. Не робіть цього. Гото працює набагато краще.
Девід Торнлі

14

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


8

Ви можете перевести свій перемикач в окрему функцію, як це:

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.


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**
    }
}

2

Ні, C ++ не має для цього конструкції, враховуючи, що ключове слово "break" вже зарезервоване для виходу з блоку комутації. Як альтернатива може бути достатньо до.. while () з прапором виходу.

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

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, і якщо ЯК перевірити ваш стан для виходу з циклу .......... так просто, як це може бути


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

Мене дивує, як просто це враховує глибину пояснень ... Ось усе, що вам потрібно ...

bool imLoopin = true;

while(imLoopin) {

    switch(msg->state) {

        case MSGTYPE: // ... 
            break;

        // ... more stuff ...

        case DONE:
            imLoopin = false;
            break;

    }

}

ЛОЛ!! Дійсно! Це все, що вам потрібно! Одна додаткова змінна!


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**
}
}

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

1
На жаль, ваша пам'ять несправна. Це було б приємною особливістю в тих рідкісних випадках, коли це було б корисно. (Я бачив пропозиції щодо кількості рівнів, з яких можна вирватися, але вони виглядають як помилки, які очікують на це.)
Девід Торнлі

То, мабуть, була Java. Дякую за відповідь. І, звичайно, ти маєш рацію і з рештою.
Андрій Вайна II,

-3
  while(true)
  {
    switch(x)
    {
     case 1:
     {
      break;
     }
    break;
   case 2:
    //some code here
   break;
  default:
  //some code here
  }
}

1
Усі перерви в цьому зразку коду виходять з оператора перемикання.
Dan Hulme
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.