Pętla, która zawsze wykonuje się raz
Pętla while sprawdza, zanim zadziała, więc może wykonać się zero razy. Zwykle tak właśnie powinno być. Czasem jednak to dokładnie odwrotność tego, czego trzeba: nie wiesz, czy powtórzyć pytanie, dopóki go nie pokażesz, i nie ocenisz wejścia użytkownika, dopóki czegoś nie wczytasz. do-while przenosi test na koniec, więc ciało wykonuje się najpierw, a warunek decyduje tylko o tym, czy wykonać je jeszcze raz.
Warunek i < 5 jest fałszywy od początku. Pętla while wykonuje się zero razy; do-while dokładnie raz. To cała różnica między nimi.
Składnia i ten średnik
do {
// ciało: zawsze wykonuje się co najmniej raz
} while (warunek);
Kolejność czyta się tak, jak przebiega wykonanie: wykonaj ciało, a dopóki warunek jest spełniony, wykonaj je znowu.
Średnik po nawiasie zamykającym jest obowiązkowy. do ... while (cond); to jedna instrukcja, a instrukcje w C kończą się średnikiem. Pomiń go, a kompilator będzie czytał dalej i zgłosi błąd składni gdzieś niżej, dlatego prawdziwa pomyłka często jest kilka linii od tej, którą wskazuje komunikat.
To także jedyne miejsce w C, gdzie while (cond); ze średnikiem jest poprawne, a nie jest błędem. W zwykłej pętli while ta sama interpunkcja tworzy puste ciało i zwykle nieskończoną pętlę; tutaj zamyka do.
Nawiasy klamrowe są opcjonalne przy ciele z jedną instrukcją, ale jak w każdej innej strukturze sterującej w C i tak je pisz.
Wzorzec walidacji wejścia
Do tego właśnie zaprojektowano do-while. Musisz coś wczytać, zanim ocenisz, czy to jest do przyjęcia, więc odczyt musi nastąpić przed testem:
Pętla wczytuje dalej, aż wartość przejdzie test, a ten sam warunek, który odrzuca złą wartość, prosi o kolejny obieg. Zapisany jako while, odczyt musiałby pojawić się dwa razy: raz przed pętlą, żeby przygotować warunek, i raz na końcu ciała. Właśnie to powtórzenie usuwa do-while.
Przy prawdziwym wejściu ciało wywoływałoby scanf:
int age;
do {
printf("Podaj swoj wiek (1-120): ");
if (scanf("%d", &age) != 1) {
while (getchar() != '\n') { } /* odrzuć złą linię */
age = -1; /* wymuś kolejny obieg */
}
} while (age < 1 || age > 120);
Zwróć uwagę na wewnętrzną pętlę while, która opróżnia resztę linii. Jeśli scanf nie zdoła przekonwertować danych, problematyczny tekst nadal siedzi w buforze wejścia i każdy kolejny odczyt też się na nim wywraca: pętla kręci się w nieskończoność, wypisując pytanie. Wyczyszczenie bufora jest częścią wzorca.
Wzorzec menu
Drugie klasyczne zastosowanie: pokaż opcje, wczytaj wybór, wykonaj go i powtarzaj, dopóki użytkownik nie wyjdzie. Menu musi pojawić się, zanim będzie jakikolwiek wybór do sprawdzenia.
break wewnątrz switch kończy switch, a nie pętlę: warto być tu precyzyjnym, bo oba używają tego samego słowa kluczowego. Tutaj pętla kończy się, bo choice != 4 staje się fałszem. Żeby naprawdę opuścić pętlę z wnętrza switch, trzeba użyć return albo zmiennej-flagi; sam break tego nie zrobi.
Gwarantowany pierwszy obieg może być błędem
Ponieważ ciało wykonuje się przed jakimkolwiek testem, do-while chętnie przetworzy dane, które nie istnieją:
/* BUG: wypisuje a[0] nawet wtedy, gdy n wynosi 0 */
int i = 0;
do {
printf("%d\n", a[i]);
i++;
} while (i < n);
Przy n == 0 to czyta a[0] z pustej tablicy: niezdefiniowane zachowanie. Każda pętla po kolekcji, która może być pusta, potrzebuje while albo for, gdzie test jest na początku.
Zasada ogólna: używaj do-while tylko wtedy, gdy wykonanie zero razy jest niemożliwe z natury problemu, a nie tylko mało prawdopodobne w praktyce. Pytanie użytkownika o dane, rzucanie kostką, aż wypadnie docelowa liczba, generowanie i sprawdzanie kandydata: tych rzeczy naprawdę nie da się ocenić przed pierwszym obiegiem. Przechodzenie po liście nie może tego o sobie powiedzieć.
break i continue w do-while
Oba działają, a continue ma haczyk, który warto znać: skacze do sprawdzenia warunku, a nie na początek ciała.
i++ na początku ciała wykonuje się w każdym obiegu, również w tych przerwanych przez continue, więc pętla dalej postępuje. Umieść aktualizację za continue, a pętla się zawiesi: to ta sama pułapka, którą mają pętle while.
break natychmiast opuszcza pętlę i całkowicie pomija warunek.
Wybór spośród trzech pętli
Trzy pętle w C mają równe możliwości; wybór dotyczy tego, która najlepiej wyraża intencję.
for licznik, jego limit i krok należą do siebie: "n razy"
while powtarzaj, aż zmieni się warunek, może ani razu: "dopóki"
do-while to samo, ale pierwszy obieg jest gwarantowany: "co najmniej raz"
W praktyce do-while jest zdecydowanie najrzadziej używaną z trzech. Większość pętli albo liczy, albo musi obsłużyć przypadek pusty, a w obu sytuacjach test powinien być na górze. Sięgaj po do-while, gdy przyłapiesz się na powielaniu instrukcji tuż przed pętlą while tylko po to, żeby przygotować jej warunek: to powtórzenie jest sygnałem.
Najczęściej zadawane pytania
Jak napisać pętlę do-while w C?
do { ciało } while (warunek);: najpierw ciało, potem test, a całą instrukcję kończy średnik. Ciało zawsze wykonuje się co najmniej raz, a potem powtarza się, dopóki warunek jest niezerowy.
Jaka jest różnica między while a do-while w C?
Miejsce, w którym stoi test. Pętla while sprawdza warunek przed pierwszym obiegiem, więc może wykonać się zero razy. do-while sprawdza go po obiegu, więc zawsze wykonuje się co najmniej raz. Wybieraj do-while tylko wtedy, gdy ten gwarantowany pierwszy obieg jest naprawdę tym, czego wymaga problem.
Dlaczego pętla do-while wymaga średnika na końcu?
Bo do ... while (cond); to jedna instrukcja, a jak każda inna instrukcja w C kończy się średnikiem. Jego pominięcie daje mylący błąd składni wskazujący na następną linię, bo kompilator czyta dalej w poszukiwaniu reszty instrukcji.
Kiedy używać pętli do-while?
Gdy ciało musi się wykonać, zanim w ogóle da się stwierdzić, czy powtarzać: pokazanie menu przed odczytaniem wyboru, ponowne pytanie aż do uzyskania poprawnego wejścia albo wygenerowanie wartości i jej sprawdzenie. Jeśli warunek można sprawdzić z góry, pętla while wyraża to prościej.