Dwa sposoby na zmianę przebiegu pętli
Warunek pętli decyduje, kiedy się ona kończy, ale czasem prawdziwa odpowiedź pojawia się w środku ciała. Przeszukujesz tablicę i znajdujesz element na trzeciej pozycji: po co sprawdzać pozostałe 997? Albo przetwarzasz listę i jeden wpis jest pusty: pomiń go i idź dalej.
C ma po jednym słowie kluczowym na każdy przypadek:
break: opuść pętlę teraz. Wykonanie wraca do pierwszej instrukcji za nią.continue: porzuć tylko ten obieg. Przejdź do następnego.
Pierwsza pętla wypisuje 1 2 3 4 i się zatrzymuje. Druga wypisuje 1 2 3 4 6 7 8 9 10: pominęła jedną wartość i poszła dalej.
break w pętli
Kanoniczne zastosowanie to wyszukiwanie, które kończy się zaraz po sukcesie:
Ponieważ found jest ustawiane przed break, kod za pętlą potrafi odróżnić "znaleziono" od "skończyły się elementy". Taka para, czyli zmienna na wynik plus break, to standardowy idiom wyszukiwania w C. Sam licznik pętli nie nadaje się do użycia później, gdy jest zadeklarowany w nagłówku, bo wychodzi z zasięgu przy nawiasie zamykającym.
break zawsze opuszcza jedną pętlę: najbliższe otaczające for, while, do-while albo switch.
continue w pętli
continue służy do wpisów, które należy pominąć, zamiast przerywać pracę:
To, dokąd skacze continue, zależy od pętli:
for -> do części aktualizującej nagłówka (licznik i tak się zwiększa)
while -> do sprawdzenia warunku (nic więcej z ciała się nie wykona)
do-while -> do sprawdzenia warunku na dole
Ta różnica nie jest akademicka. W pętli for instrukcja continue nigdy nie pominie inkrementacji licznika, bo inkrementacja jest w nagłówku. W pętli while może:
/* BUG: continue pomija i++, więc to zawiesza się na pierwszej liczbie parzystej */
int i = 0;
while (i < 10) {
if (i % 2 == 0) {
continue;
}
printf("%d ", i);
i++;
}
Przenieś i++ nad continue albo zapisz to jako pętlę for, w której nagłówek nie pozwala się pomylić.
break w switch to nie break w pętli
break kończy najbliższą otaczającą pętlę albo switch, zależnie od tego, co jest bliżej. Umieść switch w pętli, a każdy break w nim należy do switch:
Polecenie q wypisuje swój komunikat, a pętla dalej przechodzi przez cc. Jeśli wyjście miało zakończyć pętlę, ten break tego nie zrobił. Rozwiązania to flaga, goto albo przeniesienie pętli do funkcji i użycie return; wszystkie pokazane są niżej.
Wychodzenie z zagnieżdżonych pętli
Pojedynczy break opuszcza jeden poziom. W dwóch zagnieżdżonych pętlach kończy tylko wewnętrzną, a zewnętrzna zaczyna kolejny obieg, jakby nic się nie stało.
C nie ma break 2 ani break z etykietą. Są trzy uczciwe opcje:
Flaga. Przenośna, oczywista i trochę gadatliwa:
Flaga pojawia się w dwóch miejscach, w zewnętrznym warunku i w przypisaniu, i to jest jej koszt. Przy trzech poziomach zagnieżdżenia robi się naprawdę nieprzyjemnie.
goto. Mocno oczerniany skok w C, a to jedyny przypadek, w którym doświadczeni programiści C używają go bez przepraszania:
goto w przód do etykiety tuż za pętlami robi dokładnie to, co break z etykietą w innych językach, w jednej linii i bez flagi, którą trzeba utrzymywać w zgodzie. Złą reputację goto zawdzięcza skokom wstecz i skokom do środka innych bloków, które rzeczywiście dają nieczytelny przepływ sterowania. Pojedynczy skok w przód z zagnieżdżonych pętli to nie to samo. Jądro Linuksa używa tego samego wzorca w ścieżkach sprzątania. Używaj go świadomie i oszczędnie; nie pozwól, żeby tabu zepchnęło cię do rozwiązania z trzema flagami, które czyta się trudniej.
Funkcja i return. Zwykle najlepsza odpowiedź, bo wyszukiwanie zasługuje na nazwę:
return zamyka wszystkie pętle w funkcji, nie potrzebuje flagi ani etykiety, a kod wyszukiwania ma teraz nazwę i można go testować osobno. Gdy zagnieżdżenie jest na tyle głębokie, że wyjście z niego sprawia kłopot, często jest to wskazówka, że ten blok i tak chciał być funkcją.
Kiedy pomagają, a kiedy szkodzą
break i continue zasługują na swoje miejsce, gdy pozwalają pętli powiedzieć "ten przypadek jest załatwiony" raz, blisko początku, zamiast owijać całe ciało w if. Porównaj:
/* z continue: właściwa praca nie jest wcięta */
for (int i = 0; i < n; i++) {
if (!isValid(a[i])) continue;
if (isDuplicate(a[i])) continue;
process(a[i]);
}
/* bez niego: każdy warunek ochronny dodaje poziom */
for (int i = 0; i < n; i++) {
if (isValid(a[i])) {
if (!isDuplicate(a[i])) {
process(a[i]);
}
}
}
Szkodzą, gdy jest ich dużo. Pętla z pięcioma rozrzuconymi break i trzema continue ma warunek wyjścia, który nigdzie nie jest zapisany: czytelnik musi przesymulować całe ciało, żeby wiedzieć, kiedy się zatrzyma. Dwie zasady, które dobrze się sprawdzają: trzymaj warunki z continue razem na początku ciała, a jeśli pętla ma więcej niż jeden czy dwa break, zastanów się, czy tej pracy nie powinien wykonywać warunek w nagłówku.
Dwie mniejsze pułapki. continue w do-while skacze do sprawdzenia na dole, które i tak jest następne: nieszkodliwe, ale zaskakuje. A break poza jakąkolwiek pętlą czy switch to błąd kompilacji, czyli jedyna pomyłka z tej strony, którą kompilator wyłapie za ciebie.
Najczęściej zadawane pytania
Jaka jest różnica między break a continue w C?
break całkowicie kończy pętlę i wykonanie wraca za nią. continue porzuca tylko bieżący obieg i przechodzi do następnego: w pętli for do części aktualizującej, a w while i do-while od razu do sprawdzenia warunku.
Jak wyjść z zagnieżdżonej pętli w C?
Pojedynczy break opuszcza tylko najbardziej wewnętrzną pętlę. Żeby wyjść z obu, ustaw flagę i sprawdzaj ją w warunku pętli zewnętrznej, użyj goto, by skoczyć do etykiety za pętlami, albo, co zwykle jest najczystsze, przenieś pętle do funkcji i użyj return.
Czy break wychodzi z pętli, czy tylko ze switch?
Z tego, co otacza go najbliżej. break wewnątrz switch, który jest w pętli, kończy tylko switch; pętla działa dalej. Żeby opuścić pętlę z wnętrza switch, potrzebujesz flagi, goto albo return.
Dlaczego continue powoduje nieskończoną pętlę while?
Bo continue skacze do warunku i pomija resztę ciała, łącznie z inkrementacją licznika, jeśli stoi na końcu. W pętli for aktualizacja jest w nagłówku, więc wykonuje się zawsze; w pętli while przenieś aktualizację przed continue albo użyj for.