Czy mogę użyć break, aby wyjść z wielu zagnieżdżonych pętli „for”?


305

Czy można użyć tej breakfunkcji, aby wyjść z kilku zagnieżdżonych forpętli?

Jeśli tak, jak byś to zrobił? Czy możesz również kontrolować, ile pętli breakwychodzi?


1
Zamiast używać break lub goto do zamykania wielu zagnieżdżonych pętli, możesz zawrzeć tę konkretną logikę w funkcji i użyć return, aby wyjść z wielu zagnieżdżonych pętli. Pozwoli to zachować estetykę kodu i uniemożliwi korzystanie z goto, co jest złą praktyką programistyczną.
Rishab Shinghal

Odpowiedzi:


239

AFAIK, C ++ nie obsługuje pętli nazewnictwa, podobnie jak Java i inne języki. Możesz użyć goto lub utworzyć używaną wartość flagi. Na końcu każdej pętli sprawdź wartość flagi. Jeśli jest ustawiony na true, możesz przerwać tę iterację.


317
Nie bój się użyć, gotojeśli jest to najlepsza opcja.
klucze od

18
Jestem nowym programistą C ++ (i jednym bez formalnego szkolenia programistycznego), więc po przeczytaniu o ludziach na goto. Waham się przed użyciem go w obawie, że mój program może nagle eksplodować i mnie zabić. Poza tym, kiedy pisałem programy na moim ti-83 (oczywiście w nudnej klasie matematycznej), funkcje dostarczone przez podstawowy edytor wymagały użycia goto.
Faken,

26
@Faken: Używają dwóch typów programistów goto: złych programistów i programistów pragmatycznych. Te pierwsze są oczywiste. Ten ostatni, do którego pasowałbyś, gdybyś zdecydował się je dobrze wykorzystać, stosuje tak zwaną koncepcję „zła”, gdy jest to mniejsze (dwa) zło. Przeczytaj to, aby lepiej zrozumieć niektóre koncepcje języka C ++, które mogą być potrzebne od czasu do czasu (makra, goto's, preprocesor, tablice): parashift.com/c++-faq-lite/big-picture.html#faq-6.15
jkeys,

41
@Faken: Nie ma nic złego w korzystaniu z goto. To nadużywanie goto że jest kłopotliwe.
Wszyscy

28
@Hooked: Zgadza się, z tym, że używanie gotorzadko jest najlepszą opcją. Dlaczego nie zastosować pętli do ich własnej funkcji ( inlinejeśli martwisz się szybkością) i returnod tego?
sbi

265

Nie, nie psuj tego za pomocą break. To ostatnia zachowana warownia do użytku goto.


19
Standard kodowania MISRA C ++ pozwala na użycie goto do pokrycia dokładnie tego rodzaju sytuacji.
Richard Corden,

11
Prawdziwi programiści nie boją się używać goto. Wykonano to przez 25 lat - nie żałuję - zaoszczędziłem mnóstwo czasu na rozwój.
Doug Null,

1
Zgadzam się. Gotos jest koniecznością, jeśli nie masz 8K pikseli i musisz wywołać sekwencję 30 wywołań systemu operacyjnego Windows.
Michaël Roy,

Lub prosty „powrót” poprzez wstawienie kodu do funkcji.
Tara,

68

Aby dodać jednoznaczną odpowiedź za pomocą lambdas:

  for (int i = 0; i < n1; ++i) {
    [&] {
      for (int j = 0; j < n2; ++j) {
        for (int k = 0; k < n3; ++k) {
          return; // yay we're breaking out of 2 loops here
        }
      }
    }();
  }

Oczywiście ten wzorzec ma pewne ograniczenia i oczywiście tylko C ++ 11, ale myślę, że jest całkiem użyteczny.


7
To może mylić czytelnika z myśleniem, że powrót powoduje funkcję, którą zwraca lambda, a nie samą lambda.
Xunie,

3
@Xunie: Jeśli twoja pętla jest tak skomplikowana, że ​​zapominasz, że jesteś w lambdzie, czas nadać jej inną funkcję, ale w prostych przypadkach powinno to działać całkiem dobrze.
MikeMB

17
Myślę, że to rozwiązanie jest piękne
Phuket

Możesz uchwycić lambda w szablonie RIIA, który nadaje mu nazwę i nazywa go dtor (aka scope gaurd). Nie zwiększy to wydajności, ale może poprawić czytelność i zmniejszyć ryzyko pominięcia nawiasu wywołania funkcji.
Red.Wave

56

Innym podejściem do wyrwania się z zagnieżdżonej pętli jest podzielenie obu pętli na osobną funkcję i returnod tej funkcji, gdy chcesz wyjść.

Oczywiście rodzi to kolejny argument, czy należy kiedykolwiek jawnie odwoływać się returndo funkcji w dowolnym miejscu innym niż na końcu.


7
To jest problem C. Dzięki RIAA wczesny zwrot nie stanowi problemu, ponieważ wszystkie problemy związane z wczesnym zwrotem są poprawnie obsługiwane.
Martin York,

4
Rozumiem, że prawidłowe zastosowanie RIAA może rozwiązać problem czyszczenia zasobów w C ++, ale widziałem filozoficzny argument przeciwko wczesnemu powrotowi w innych środowiskach i językach. Jeden system, nad którym pracowałem, w którym standard kodowania zabraniał wczesnego powrotu, zawierał funkcje wypełnione zmiennymi logicznymi (o nazwach podobnych continue_processing), które kontrolowały wykonywanie bloków kodu w dalszej części funkcji.
Greg Hewgill,

21
Co to jest RIAA? Czy to coś takiego jak RAII? = D
klucze od

1
Zależy, ile ma pętli i jak głębokie jest gniazdo ... chcesz niebieską pigułkę czy czerwoną pigułkę?
Matt

34

break wyjdzie tylko z najbardziej wewnętrznej pętli ją zawierającej.

Możesz użyć goto, aby wyjść z dowolnej liczby pętli.

Oczywiście goto jest często uważane za szkodliwe .

czy właściwe jest użycie funkcji break [...]?

Korzystanie z break i goto może utrudnić wnioskowanie o poprawności programu. Zobacz tutaj dyskusję na ten temat: Dijkstra nie była szalona .


16
Dobra odpowiedź, ponieważ wyjaśnia, że ​​„goto jest szkodliwy” mem jest silnie powiązany z bardziej ogólnym stwierdzeniem „przerwanie przepływu sterowania jest szkodliwe”. Nie ma sensu mówić „goto jest szkodliwy”, a następnie odwrócić się i zalecić użycie breaklub return.
Pavel Minaev

6
@Pavel: breaki returnmają tę przewagę goto, że nie musisz szukać etykiety, aby znaleźć kierunek . Tak, pod nimi są jakieś goto, ale bardzo ograniczone. Są one o wiele łatwiejsze do rozszyfrowania przez mózg dopasowujący wzorce programisty niż nieograniczony goto. Więc IMO są lepsze.
sbi

@sbi: Prawda, ale przerwa wciąż nie jest częścią programowania strukturalnego. Jest po prostu lepiej tolerowany niż goto.
klucze od

2
@KarlVoigtland link Dijkstra jest nieaktualny; wygląda na to, że działa: blog.plover.com/2009/07
Aaron Brager

3
W tej sytuacji nie ma absolutnie nic złego w korzystaniu z goto. Dobrze umieszczone goto jest lepsze i bardziej czytelne niż wiele innych przekręconych rozwiązań, które zostałyby zaproponowane w inny sposób.
James

22

Chociaż ten answear został już zaprezentowany, myślę, że dobrym podejściem jest wykonanie następujących czynności:

for(unsigned int z = 0; z < z_max; z++)
{
    bool gotoMainLoop = false;
    for(unsigned int y = 0; y < y_max && !gotoMainLoop; y++)
    {
        for(unsigned int x = 0; x < x_max && !gotoMainLoop; x++)
        {
                          //do your stuff
                          if(condition)
                            gotoMainLoop = true;
        }
    }

}

5
co jest dobre, ale wciąż nie tak czytelne, wolałbym goto w takim przypadku
Петър Петров

2
powoduje to, że kod jest „dość” wolny, ponieważ gotoMainLoopjest sprawdzany co cykl
Thomas

1
W takim przypadku użycie wartości rzeczywistej gotoczyni rdzeń bardziej czytelnym i wydajniejszym.
Петър Петров

20

Co powiesz na to?

for(unsigned int i=0; i < 50; i++)
{
    for(unsigned int j=0; j < 50; j++)
    {
        for(unsigned int k=0; k < 50; k++)
        {
            //Some statement
            if (condition)
            {
                j=50;
                k=50;
            }
        }
    }
}

2
ciekawe podejście, ale zdecydowanie podoba mi się sposób, w jaki ered @ inf.ig.sh sobie z tym radzi. for (unsigned int y = 0; y <y_max &&! gotoMainLoop; y ++).
Andy

15

Przykład kodu używającego gotoi etykiety do wyrwania się z zagnieżdżonej pętli:

for (;;)
  for (;;)
    goto theEnd;
theEnd:

11

Jednym z dobrych sposobów na wyrwanie się z kilku zagnieżdżonych pętli jest przefaktoryzowanie kodu na funkcję:

void foo()
{
    for(unsigned int i=0; i < 50; i++)
    {
        for(unsigned int j=0; j < 50; j++)
        {
            for(unsigned int k=0; k < 50; k++)
            {
                // If condition is true
                return;
            }
        }
    }
}

4
... co nie jest opcją, jeśli musimy przekazać 10-20 zmiennych dla stosu tej funkcji w ramce.
Петър Петров

1
@ ПетърПетров, a następnie wybierz lambda, która jest również lepsza, ponieważ możesz zdefiniować ją dokładnie tam, gdzie jej potrzebujesz.
DarioP,

+1 dla lambdów, ale remont rdzenia silnika gry, w którym nawet jedna rama stosu jest nadal wąskim gardłem. Przykro mi to mówić, ale lambdy nie są tak lekkie przynajmniej w MSVC 2010.
Петър Петров

@ ПетърПетров Następnie zmień parę funkcji na klasę, a zmienne stosu na prywatne elementy.
Arthur Tacca,

To tylko komplikuje i tak już złożony kod :) Czasami goto jest jedynym rozwiązaniem. Lub jeśli piszesz skomplikowane automaty z dokumentacją „goto state X”, wtedy goto powoduje, że kod jest odczytywany tak, jak zapisano w dokumencie. Ponadto C # i go oba mają goto z przeznaczeniem: żaden język nie jest kompletny bez goto, a goto są często najlepiej używanymi narzędziami do pisania pośredniego tłumacza lub kodu podobnego do asemblera.
Петър Петров

5

goto może być bardzo pomocny w zrywaniu zagnieżdżonych pętli

for (i = 0; i < 1000; i++) {
    for (j = 0; j < 1000; j++) {
        for (k = 0; k < 1000; k++) {
             for (l = 0; l < 1000; l++){
                ....
                if (condition)
                    goto break_me_here;
                ....
            }
        }
    }
}

break_me_here:
// Statements to be executed after code breaks at if condition

3

breakOświadczenie kończy wykonanie najbliższym otaczającej do, for, switchlub whileoświadczenie, w którym się pojawia. Kontrola przechodzi do instrukcji następującej po instrukcji zakończonej.

z msdn .


3

Myślę, że a gotojest ważne w takich okolicznościach:

Aby symulować break/ continue, chcesz:

Złamać

for ( ;  ;  ) {
    for ( ;  ;  ) {
        /*Code here*/
        if (condition) {
            goto theEnd;
        }
    }
}
theEnd:

Kontyntynuj

for ( ;  ; ) {
    for ( ;  ;  ) {
        /*Code here*/
        if (condition) {
            i++;
            goto multiCont;
        }
    }
    multiCont:
}

„Kontynuuj” nie będzie tam działać, ponieważ wyrażenie iteracyjne pierwszej pętli nie zostanie wykonane
Fabio A.

Zakładam, że iteratorem dla pierwszej pętli jest i. Stąd i++przed goto
JuliusAlphonso

0

Inne języki, takie jak PHP, akceptują parametr break (tj. Break 2;), aby określić liczbę poziomów zagnieżdżonych pętli, z których chcesz się zerwać, C ++ jednak tego nie robi. Będziesz musiał to wypracować, używając logicznej wartości, którą ustawiłeś na false przed pętlą, ustaw na true w pętli, jeśli chcesz przerwać, oraz warunkową przerwę po zagnieżdżonej pętli, sprawdzając, czy logiczna została ustawiona na true i złamać, jeśli tak.


0

Wiem, że to stary post. Sugerowałbym jednak nieco logiczną i prostszą odpowiedź.

for(unsigned int i=0; i < 50; i++)
    {
        for(unsigned int j=0; j < conditionj; j++)
        {
            for(unsigned int k=0; k< conditionk ; k++)
            {
                // If condition is true

                j= conditionj;
               break;
            }
        }
    }

3
To niezbyt skalowalne rozwiązanie, ponieważ j = conditionjnie zadziała, jeśli zamiast tego masz złożony predykat j < conditionj.
Sergey

0

Przerwij dowolną liczbę pętli za pomocą tylko jednej boolzmiennej, patrz poniżej:

bool check = true;

for (unsigned int i = 0; i < 50; i++)
{
    for (unsigned int j = 0; j < 50; j++)
    {
        for (unsigned int k = 0; k < 50; k++)
        {
            //Some statement
            if (condition)
            {
                check = false;
                break;
            }
        }
        if (!check)
        {
            break;
        }
    }
    if (!check)
    {
        break;
    }
}

W tym kodzie mamy break;wszystkie pętle.


0

Nie jestem pewien, czy warto, ale możesz emulować nazwane pętle Java za pomocą kilku prostych makr:

#define LOOP_NAME(name) \
    if ([[maybe_unused]] constexpr bool _namedloop_InvalidBreakOrContinue = false) \
    { \
        [[maybe_unused]] CAT(_namedloop_break_,name): break; \
        [[maybe_unused]] CAT(_namedloop_continue_,name): continue; \
    } \
    else

#define BREAK(name) goto CAT(_namedloop_break_,name)
#define CONTINUE(name) goto CAT(_namedloop_continue_,name)

#define CAT(x,y) CAT_(x,y)
#define CAT_(x,y) x##y

Przykładowe użycie:

#include <iostream>

int main()
{
    // Prints:
    // 0 0
    // 0 1
    // 0 2
    // 1 0
    // 1 1

    for (int i = 0; i < 3; i++) LOOP_NAME(foo)
    {
        for (int j = 0; j < 3; j++)
        {
            std::cout << i << ' ' << j << '\n';
            if (i == 1 && j == 1)
                BREAK(foo);
        }
    }
}

Inny przykład:

#include <iostream>

int main()
{
    // Prints: 
    // 0
    // 1
    // 0
    // 1
    // 0
    // 1

    int count = 3;
    do LOOP_NAME(foo)
    {
        for (int j = 0; j < 3; j++)
        {
            std::cout << ' ' << j << '\n';
            if (j == 1)
                CONTINUE(foo);
        }
    }
    while(count-- > 1);
}

-1
  while (i<n) {
    bool shouldBreakOuter = false;
    for (int j=i + 1; j<n; ++j) {
      if (someCondition) {
          shouldBreakOuter = true;
      }
    }

    if (shouldBreakOuter == true)
      break;

  }

-3

Możesz użyć try ... catch.

try {
    for(int i=0; i<10; ++i) {
        for(int j=0; j<10; ++j) {
            if(i*j == 42)
                throw 0; // this is something like "break 2"
        }
    }
}
catch(int e) {} // just do nothing
// just continue with other code

Jeśli musisz wyrwać się z kilku pętli jednocześnie, często jest to i tak wyjątek.


1
Chciałbym poznać powód, dla którego ta odpowiedź ma tak wiele głosów negatywnych.
hkBattousai

6
@hkBattousai Rozwiązanie ma mniej głosów, ponieważ używa wyjątku do kontrolowania przepływu wykonania. Jak sama nazwa wskazuje, wyjątki należy stosować tylko w wyjątkowych przypadkach.
Helio Santos,

4
@HelioSantos Czy nie jest to wyjątkowa sytuacja, dla której język nie zapewnia właściwego rozwiązania?
hkBattousai

8
Wyjątki są powolne.
Gordon

2
Wpływ rzutu na wydajność jest ogromny w przypadku czegoś, co nie jest nieodwracalnym błędem w 99% przypadków.
Michaël Roy

-4

Wyłamanie się z pętli for jest dla mnie trochę dziwne, ponieważ semantyka pętli for zwykle wskazuje, że wykona ona określoną liczbę razy. Jednak nie jest to złe we wszystkich przypadkach; jeśli szukasz czegoś w kolekcji i chcesz się zepsuć po jego znalezieniu, jest to przydatne. Przełamywanie zagnieżdżonych pętli nie jest jednak możliwe w C ++; jest w innych językach poprzez użycie przerwy oznaczonej etykietą. Możesz użyć etykiety i goto, ale może to spowodować zgagę w nocy ..? Wydaje się jednak najlepszą opcją.


11
To wcale nie jest dziwne. Jeśli iterujesz po kolekcji, by czegoś szukać (i nie masz szybszego sposobu wyszukiwania), nie ma sensu kończyć pętli. (jako przykład)
Joe,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.