Jak wyrwać się z pętli z wnętrza przełącznika?

113

Piszę kod, który wygląda następująco:

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

Czy jest na to bezpośredni sposób?

Wiem, że mogę użyć flagi i przerwać pętlę, wstawiając warunkową przerwę tuż po przełączeniu. Chcę tylko wiedzieć, czy C ++ ma już jakąś konstrukcję do tego.

jrharshath
źródło
20
Dlaczego potrzebujesz warunkowej przerwy po zmianie? Po prostu zmień czas z while (true) na while (flag) ...
Tal Pressman,
7
@Dave_Jarvis Zakładam, że jest to uproszczona wersja, którą umieścił tutaj, aby zilustrować, co próbował zrobić.
Alterlife
6
Jeśli jesteś jednym z tych programistów, którzy tworzą funkcje składające się z kilku stron, znajdziesz gotoatrakcyjne i czasami jedyne proste wyjście. Jeśli masz tendencję do organizowania kodu w małe funkcje, które mają tylko kilka wierszy i każda z nich wykonuje jedną rzecz, nigdy nie napotkasz tego problemu. (Nawiasem mówiąc, twój kod też będzie łatwiejszy do odczytania.)
sbi
13
Jeśli masz ochotę na poradę, jak rzucić palenie, jedyne, co chcesz wiedzieć, to jak dostać się do najbliższej stacji metra.
Michael Krelin - haker
7
@hacker: Cóż, jeśli nie widzisz stacji metra przed sobą z powodu całego dymu, ta rada może nie być taka zła. :)
sbi

Odpowiedzi:

49

Przesłanka

Poniższy kod należy uznać za zły format, niezależnie od języka lub pożądanej funkcjonalności:

while( true ) {
}

Argumenty wspierające

while( true )Pętla jest słaba postać ponieważ:

  • Przerywa domniemany kontrakt pętli while.
    • Deklaracja pętli while powinna jawnie określać jedyny warunek wyjścia.
  • Sugeruje, że zapętla się w nieskończoność.
    • Aby zrozumieć klauzulę kończącą, należy odczytać kod w pętli.
    • Pętle, które powtarzają się w nieskończoność, uniemożliwiają użytkownikowi zakończenie programu z poziomu programu.
  • Jest nieefektywny.
    • Istnieje wiele warunków zakończenia pętli, w tym sprawdzanie wartości „prawda”.
  • Jest podatny na błędy.
    • Nie można łatwo określić, gdzie umieścić kod, który będzie zawsze wykonywany dla każdej iteracji.
  • Prowadzi do niepotrzebnie złożonego kodu.
  • Automatyczna analiza kodu źródłowego.
    • W celu znalezienia błędów, analizy złożoności programu, kontroli bezpieczeństwa lub automatycznego wyprowadzenia dowolnego innego zachowania kodu źródłowego bez wykonywania kodu, określenie początkowych warunków zerwania umożliwia algorytmom określenie przydatnych niezmienników, poprawiając w ten sposób wskaźniki automatycznej analizy kodu źródłowego.
  • Nieskończone pętle.
    • Jeśli wszyscy zawsze używają while(true)pętli, które nie są nieskończone, tracimy zdolność do zwięzłego komunikowania się, gdy pętle w rzeczywistości nie mają warunku zakończenia. (Prawdopodobnie to już się stało, więc kwestia jest dyskusyjna.)

Alternatywa dla „Idź do”

Poniższy kod jest lepszą formą:

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

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

Zalety

Bez flagi. Nie goto. Bez wyjątku. Łatwe do zmiany. Łatwe do odczytania. Łatwe do naprawienia. Dodatkowo kod:

  1. Izoluje wiedzę o obciążeniu pętli od samej pętli.
  2. Pozwala osobie obsługującej kod na łatwe rozszerzenie funkcjonalności.
  3. Umożliwia przypisanie wielu warunków zakończenia w jednym miejscu.
  4. Oddziela klauzulę kończącą od kodu do wykonania.
  5. Jest bezpieczniejszy dla elektrowni jądrowych. ;-)

Druga kwestia jest ważna. Nie wiedząc, jak działa kod, gdyby ktoś poprosił mnie o zrobienie głównej pętli, aby inne wątki (lub procesy) miały trochę czasu procesora, przychodzą mi na myśl dwa rozwiązania:

Opcja 1

Łatwo wstaw pauzę:

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

Opcja 2

Zastąp wykonaj:

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

Ten kod jest prostszy (dzięki temu łatwiejszy do odczytania) niż pętla z osadzonym switch. isValidStateMetoda powinna określić tylko wtedy, gdy pętla powinna kontynuować. Koń roboczy metody powinien zostać wyabstrahowany do executemetody, co umożliwia podklasom przesłonięcie domyślnego zachowania (trudne zadanie przy użyciu osadzonych switchi goto).

Przykład Pythona

Porównaj następującą odpowiedź (na pytanie w Pythonie), która została opublikowana w StackOverflow:

  1. Pętla na zawsze.
  2. Poproś użytkownika o wprowadzenie swojego wyboru.
  3. Jeśli wprowadzeniem użytkownika jest „restart”, kontynuuj pętlę w nieskończoność.
  4. W przeciwnym razie przestań na zawsze zapętlić.
  5. Koniec.
Kod
while True: 
    choice = raw_input('What do you want? ')

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

print 'Break!' 

Przeciw:

  1. Zainicjuj wybór użytkownika.
  2. Pętla, podczas gdy wybór użytkownika to słowo „restart”.
  3. Poproś użytkownika o wprowadzenie swojego wyboru.
  4. Koniec.
Kod
choice = 'restart';

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

print 'Break!'

Tutaj while Trueskutkuje wprowadzającym w błąd i zbyt złożonym kodem.

Dave Jarvis
źródło
45
Pomysł, który while(true)powinien być szkodliwy, wydaje mi się dziwny. Są pętle, które sprawdzają przed wykonaniem, są takie, które sprawdzają później, i są takie, które sprawdzają w środku. Ponieważ te ostatnie nie mają konstrukcji składniowej w C i C ++, będziesz musiał użyć while(true)lub for(;;). Jeśli uważasz, że to niewłaściwe, nie poświęciłeś jeszcze wystarczająco dużo uwagi różnym rodzajom pętli.
sbi
34
Powody, dla których się nie zgadzam: podczas gdy (prawda) i for (;;;) nie są mylące (w przeciwieństwie do {} while (true)), ponieważ od razu określają, co robią. W rzeczywistości łatwiej jest szukać instrukcji „break” niż analizować dowolny warunek pętli i szukać miejsca, w którym jest on ustawiony, a nie trudniej udowodnić poprawności. Argument o tym, gdzie umieścić kod, jest tak samo trudny do rozwiązania w przypadku instrukcji continue, jak i niewiele lepszy w przypadku instrukcji if. Argument efektywności jest zdecydowanie głupi.
David Thornley,
23
Ilekroć zobaczysz „while (true)”, możesz pomyśleć o pętli, która ma wewnętrzne warunki zakończenia, które nie są trudniejsze niż dowolne warunki końcowe. Po prostu pętla "while (foo)", gdzie foo jest zmienną boolowską ustawioną w dowolny sposób, nie jest lepsza niż "while (true)" z przerwami. W obu przypadkach musisz przejrzeć kod. Nawiasem mówiąc, przedstawienie głupiego powodu (a mikroefektywność to głupi argument) nie pomoże w twojej sprawie.
David Thornley,
5
@Dave: Podałem powód: to jedyny sposób na uzyskanie pętli, która sprawdza stan w środku. Klasyczny przykład: while(true) {string line; std::getline(is,line); if(!is) break; lines.push_back(line);}Oczywiście mógłbym przekształcić to w wstępnie uwarunkowaną pętlę (używając std::getlinejako warunku pętli), ale ma to same wady ( linemusiałaby to być zmienna poza zakresem pętli). A gdybym to zrobił, musiałbym zapytać: po co w ogóle mamy trzy pętle? Zawsze moglibyśmy przekształcić wszystko w wstępnie uwarunkowaną pętlę. Użyj tego, co najlepiej pasuje. Jeśli while(true)pasuje, użyj go.
sbi
3
Z grzeczności wobec osób czytających tę odpowiedź unikałbym komentowania jakości dobra, które wywołało pytanie, i pozostałbym przy udzielaniu odpowiedzi na samo pytanie. Pytanie jest odmianą następującego, jak sądzę, cecha wielu języków: masz pętlę zagnieżdżoną w innej pętli. Chcesz wyrwać się z zewnętrznej pętli z wewnętrznej pętli. Jak to się robi?
Alex Leibowitz
172

Możesz użyć goto.

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

   }
}
exit_loop: ;
Mehrdad Afshari
źródło
12
Po prostu nie oszalej z tym słowem kluczowym.
Fragsworth
45
To zabawne, że jestem źle oceniany, ponieważ ludzie nie lubią goto. OP wyraźnie wspomniano bez użycia flag . Czy możesz zaproponować lepszy sposób, biorąc pod uwagę ograniczenia PO? :)
Mehrdad Afshari,
18
przegłosowano tylko po to, by zrekompensować bezmyślnym hejterom goto. Myślę, że Mehrdad wiedział, że straci kilka punktów za zasugerowanie rozsądnego rozwiązania.
Michael Krelin - haker
6
+1, nie ma lepszego sposobu na imho, nawet biorąc pod uwagę ograniczenia PO. Flagi są brzydkie, przerywają logikę w całej pętli, a przez to trudniejsze do uchwycenia.
Johannes Schaub - litb
11
w końcu, bez konstrukcji goto, wszystkie języki zawodzą. Języki wysokiego poziomu zaciemniają to, ale jeśli spojrzysz wystarczająco dokładnie pod maskę, znajdziesz każdą pętlę lub warunek sprowadzający się do gałęzi warunkowych i bezwarunkowych. oszukujemy się, używając słów kluczowych for, while, aż, switch, if, ale ostatecznie wszystkie one używają GOTO (lub JMP) w taki czy inny sposób. możesz się łudzić, wymyślając różne sprytne sposoby, aby to ukryć, lub możesz być po prostu pragmatycznie uczciwy i używać goto, tam gdzie jest to stosowne i oszczędnie.
niezsynchronizowany
58

Alternatywnym rozwiązaniem jest użycie słowa kluczowego continuew połączeniu z breaknp .:

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

Użyj continueinstrukcji, aby zakończyć każdą etykietę przypadku, w której chcesz, aby pętla była kontynuowana, i użyj breakinstrukcji, aby zakończyć etykiety przypadku, które powinny zakończyć pętlę.

Oczywiście to rozwiązanie działa tylko wtedy, gdy po instrukcji switch nie ma dodatkowego kodu do wykonania.

sakra
źródło
9
Chociaż jest to rzeczywiście bardzo eleganckie, ma tę wadę, że większość programistów musi najpierw przyjrzeć się temu przez minutę, aby zrozumieć, jak to działa. :(
sbi
8
Ostatnie zdanie jest tym, co sprawia, że ​​nie jest to tak wspaniałe. :(W przeciwnym razie bardzo czysta implementacja.
nawfal
Kiedy się nad tym zastanowić, żaden kod komputerowy nie jest dobry. Jesteśmy przeszkoleni, aby to zrozumieć. Na przykład xml wyglądał jak śmieci, dopóki się tego nie nauczyłem. Teraz wygląda mniej śmieciowo. for (int x = 0; x <10; ++ x, moveCursor (x, y)) faktycznie spowodowało błąd logiczny w moim programie. Zapomniałem, że stan aktualizacji wystąpił na końcu.
22

Zgrabnym sposobem na zrobienie tego byłoby umieszczenie tego w funkcji:

int yourfunc() {

    while(true) {

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

    }
}

Opcjonalnie (ale „złe praktyki”): jak już sugerowano, możesz użyć goto lub zgłosić wyjątek wewnątrz przełącznika.

Alterlife
źródło
4
Wyjątek powinien być używany do rzucania wyjątku, a nie do dobrze znanego zachowania funkcji.
Clement Herreman
Zgadzam się z Afterlife: umieść to w funkcji.
dalle
14
Rzucenie wyjątku jest w tym przypadku naprawdę złe. To znaczy "Chcę użyć goto, ale gdzieś przeczytałem, że nie powinienem ich używać, więc pójdę z podstępem i będę udawać, że wyglądam sprytnie".
Gorpik
Zgadzam się, że rzucenie wyjątku lub użycie goto to okropny pomysł, ale działają one i zostały wymienione jako takie w mojej odpowiedzi.
Alterlife,
2
Zgłoszenie wyjątku jest zastępowaniem COMEFROM przez GOTO. Nie rób tego. Goto działa znacznie lepiej.
David Thornley,
14

AFAIK nie ma „podwójnego przerwania” ani podobnej konstrukcji w C ++. Najbliższa byłaby a goto- która, choć ma złe konotacje ze swoją nazwą, istnieje w języku z jakiegoś powodu - o ile jest używana ostrożnie i oszczędnie, jest to realna opcja.

Bursztyn
źródło
8

Możesz umieścić swój przełącznik w oddzielnej funkcji, takiej jak ta:

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())
    ;
Adam Pierce
źródło
7

Nawet jeśli nie lubisz goto, nie używaj wyjątku do wyjścia z pętli. Poniższy przykład pokazuje, jak brzydki może być:

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

Użyłbym gotojak w tej odpowiedzi. W takim przypadku gotokod będzie bardziej przejrzysty niż każda inna opcja. Mam nadzieję, że to pytanie będzie pomocne.

Ale myślę, że użycie gotojest tutaj jedyną opcją ze względu na ciąg while(true). Powinieneś rozważyć refaktoryzację swojej pętli. Przypuszczam, że następujące rozwiązanie:

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

Lub nawet następujące:

while ( msg->state != DONE ) {
    switch( msg->state ) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
}
Kirill V. Lyadvinsky
źródło
1
Tak, ale ja jak wyjątkami nawet mniej ;-)
quamrana
3
Używanie wyjątku do emulacji goto jest oczywiście gorsze niż faktyczne używanie goto! Prawdopodobnie będzie też znacznie wolniej.
John Carter
2
@Downvoter: Czy to dlatego, że stwierdzam, że nie należy używać wyjątków, czy dlatego, że sugeruję refaktoryzację?
Kirill V. Lyadvinsky,
1
Nie ten, kto przegrał - ale ... Twoja odpowiedź nie jest jasna, czy proponujesz, czy potępiasz pomysł wykorzystania wyjątku do wyjścia z pętli. Może pierwsze zdanie powinno brzmieć: „Nawet jeśli nie lubisz goto, nie używaj wyjątku do wyjścia z pętli:”?
Jonathan Leffler
1
@Jonathan Leffler, Dzięki, pokazałeś próbkę cennego komentarza. zaktualizował odpowiedź, pamiętając o swoich komentarzach.
Kirill V. Lyadvinsky,
5

W tym przypadku nie ma konstrukcji C ++ do wyrwania się z pętli.

Użyj flagi, aby przerwać pętlę lub (jeśli to konieczne) wyodrębnij kod do funkcji i użyj return.

ostry
źródło
2

Potencjalnie możesz użyć goto, ale wolałbym ustawić flagę, która zatrzymuje pętlę. Następnie wyłam się z przełącznika.

Martin York
źródło
2

Dlaczego po prostu nie naprawić stanu w pętli while, powodując zniknięcie problemu?

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**
    }
}
Mark B.
źródło
2

Nie, C ++ nie ma takiej konstrukcji, biorąc pod uwagę, że słowo kluczowe „break” jest już zarezerwowane na wyjście z bloku przełącznika. Alternatywnie może wystarczyć do..while () z flagą wyjścia.

do { 
    switch(option){
        case 1: ..; break;
        ...
        case n: .. ;break;
        default: flag = false; break;
    }
} while(flag);
Prashanth Sriram
źródło
1

Myślę;

while(msg->state != mExit) 
{
    switch(msg->state) 
    {
      case MSGTYPE: // ...
         break;
      case DONE:
      //  .. 
      //  ..
      msg->state =mExit;
      break;
    }
}
if (msg->state ==mExit)
     msg->state =DONE;
ercan
źródło
0

Najprostszym sposobem jest wprowadzenie prostego JEŻELI przed wykonaniem PRZEŁĄCZNIKA, a jeśli przetestujesz swój warunek wyjścia z pętli .......... tak proste, jak to tylko możliwe

SpiriAd
źródło
2
Um ... możesz być jeszcze prostszy, umieszczając ten warunek w argumencie while. Chodzi mi o to, że to jest po to! :)
Mark A. Donohoe
0

Słowo breakkluczowe w C ++ kończy tylko najbardziej zagnieżdżoną iterację lub switchinstrukcję. Dlatego nie można było wyjść z while (true)pętli bezpośrednio w switchinstrukcji; jednak możesz użyć następującego kodu, który moim zdaniem jest doskonałym wzorcem dla tego typu problemów:

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

    //...
    }
}

Jeśli trzeba było coś zrobić, gdy msg->statejest równe DONE(na przykład uruchomić procedurę czyszczenia), należy umieścić ten kod bezpośrednio po forpętli; czyli jeśli obecnie masz:

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

    //...

    case DONE:
        do_cleanup();
        break;
    }

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

    msg = next_message();
}

Następnie użyj zamiast tego:

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

    //...
    }
}

assert(msg->state == DONE);
do_cleanup();
Daniel Trebbien
źródło
0

Zdumiewa mnie, jak proste jest to, biorąc pod uwagę głębokość wyjaśnień ... Oto wszystko, czego potrzebujesz ...

bool imLoopin = true;

while(imLoopin) {

    switch(msg->state) {

        case MSGTYPE: // ... 
            break;

        // ... more stuff ...

        case DONE:
            imLoopin = false;
            break;

    }

}

LOL!! Naprawdę! To wszystko, czego potrzebujesz! Jedna dodatkowa zmienna!

Mark A. Donohoe
źródło
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
źródło
0

Mam ten sam problem i rozwiązałem go za pomocą flagi.

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;
}
Ram Suthar
źródło
-2

Jeśli dobrze pamiętam składnię C ++, możesz dodać etykietę do breakinstrukcji, tak jak w przypadku goto. Więc to, co chcesz, można łatwo napisać:

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
Andrei Vajna II
źródło
1
Niestety twoja pamięć jest wadliwa. Byłaby to fajna funkcja w tych rzadkich sytuacjach, w których byłaby przydatna. (Widziałem propozycje dotyczące liczby poziomów, z których można wyjść, ale wyglądają one jak robaki czekające na wystąpienie).
David Thornley
To musiała być więc Java. Dzięki za odpowiedź. I oczywiście masz rację z resztą.
Andrei Vajna II
-3
  while(true)
  {
    switch(x)
    {
     case 1:
     {
      break;
     }
    break;
   case 2:
    //some code here
   break;
  default:
  //some code here
  }
}
Chevaughn
źródło
1
Wszystkie przerwy w tym przykładzie kodu wyłamują się z instrukcji switch.
Dan Hulme