Gdy nie masz licznika
Pętla for jest zbudowana wokół licznika ustawianego na starcie. Wiele pętli nie ma jednak wygodnej liczby powtórzeń: czytaj liczby, dopóki nie skończą się dane, pytaj o hasło, dopóki nie będzie poprawne, dziel wartość na pół, dopóki nie osiągnie 1. W takich przypadkach naturalnym wyborem jest pętla while: po prostu powtarza, dopóki warunek pozostaje prawdziwy.
Pętla while sprawdza warunek przed każdym przebiegiem, łącznie z pierwszym. Jeśli warunek od początku jest fałszywy, ciało nie wykona się ani razu.
Warunek count > 0 jest sprawdzany jako pierwszy: jeśli jest prawdziwy, ciało się wykonuje, a potem wracamy i sprawdzamy go ponownie. Linia count-- jest tym, co w końcu czyni warunek fałszywym: usuń ją, a pętla będzie działać w nieskończoność.
Budowa pętli while
Porównaj ją ze znaną ci już trzyczęściową pętlą for. Pętla while rozdziela te trzy zadania: przygotowanie robisz przed pętlą, warunek trafia do nawiasów, a aktualizacja mieszka wewnątrz ciała.
int i = 0; // inicjalizacja: przed pętlą
while (i < 5) { // warunek: sprawdzany przy każdym przebiegu
cout << i << "\n";
i++; // aktualizacja: musisz o niej pamiętać
}
W tym ostatnim elemencie tkwi haczyk. W pętli for aktualizacja stoi tuż obok warunku, więc trudno o niej zapomnieć. W pętli while to po prostu kolejna instrukcja w ciele, a najczęstszy błąd to jej pominięcie i zawieszenie programu.
Pułapka C++, która łapie początkujących: przypadkowy średnik tuż po warunku zamienia ciało w pustą instrukcję.
int i = 0;
while (i < 5); // <-- ten średnik to całe ciało pętli
{
cout << i << "\n";
i++;
}
Taka pętla kręci się w nieskończoność, nic nie robiąc, bo ; jest jej ciałem, a i nigdy się nie zmienia. Blok w klamrach wykonałby się potem dokładnie raz. Kompilator cię nie powstrzyma: to całkowicie poprawny kod, tylko nie taki, o jaki chodziło.
do-while: ciało wykona się co najmniej raz
Czasem ciało musi się wykonać raz, zanim w ogóle da się zdecydować o powtórzeniu. Pętla do-while sprawdza warunek na końcu, więc ciało zawsze wykonuje się co najmniej raz:
Trzeba przynajmniej raz zapytać i wczytać wartość, zanim będzie wiadomo, czy dane są poprawne, a to dokładnie daje do-while. Zwróć uwagę na średnik po while (...): w C++ jest on przy do-while wymagany, a jego brak to częsty błąd kompilacji.
Różnicę względem zwykłego while wyraźnie widać, gdy warunek od początku jest fałszywy:
Wypisuje się tylko do-while body. Pętla while całkowicie pomija ciało, bo x < 5 było fałszywe już przy pierwszym sprawdzeniu.
Pętla do momentu, gdy cin zawiedzie
Klasyczne zastosowanie while to czytanie danych, dopóki się nie skończą: nie ma licznika, po prostu działasz dalej, dopóki strumień nie każe przestać. W C++ cin >> value zwraca sam strumień, który jest prawdziwy, dopóki odczyt się udaje, i fałszywy, gdy trafi na koniec danych lub niezgodność typu. Dzięki temu świetnie nadaje się na warunek pętli:
Warunek pełni podwójną rolę: w jednym kroku czyta i sprawdza. Porównaj to z wartością wartowniczą, na przykład zatrzymaniem na 0. Wtedy trzeba wczytać wartość raz przed pętlą i ponownie na końcu każdego przebiegu, żeby warunek zawsze sprawdzał świeże dane:
Bez drugiego odczytu w ciele value nigdy się nie zmienia i powstaje pętla nieskończona. Forma while (cin >> value) omija ten problem, bo włącza odczyt do warunku.
break i continue w pętlach while
break i continue działają tu tak samo jak w pętli for. break natychmiast opuszcza pętlę, a continue wraca prosto do sprawdzenia warunku, pomijając resztę bieżącego przebiegu:
Ten kod wypisuje 1 3 5 7 9. while (true) celowo nigdy nie kończy się sam z siebie: jedynym wyjściem jest break. Uważaj na continue w pętli while: aktualizacja jest częścią ciała, więc powrót na początek przez continue przed zwiększeniem licznika to cichy sposób na zawieszenie pętli. W powyższym przykładzie n++ wykonuje się jako pierwsze, więc jest bezpiecznie.
Uważaj na pętle nieskończone
Warunek musi w końcu stać się fałszywy, a to zależy wyłącznie od tego, czy coś w ciele pętli się zmienia. Dwaj typowi winowajcy to zapomniana aktualizacja i aktualizacja, która przeskakuje wartość wyjścia:
int i = 0;
while (i < 5) {
cout << i << "\n"; // i nigdy się nie zmienia -> działa w nieskończoność
}
int i = 0;
while (i != 10) {
i += 3; // 0, 3, 6, 9, 12... przeskakuje 10 -> działa w nieskończoność
}
Pierwsza pętla się zawiesza, bo i nigdy nie jest aktualizowane. Druga, bo licznik przeskakuje dokładnie tę wartość, której szuka warunek: gdy krok może nie trafić idealnie, wybieraj < lub <= zamiast !=. while (true) jest w porządku, jeśli ma gwarantowany break, a przypadkowe to po prostu błąd. Pamiętaj też o opisanej wcześniej pułapce pustego ciała while (...);: źle postawiony średnik daje pętlę nieskończoną, która na pierwszy rzut oka wygląda poprawnie.
Dalej: pętla for po zakresie
Pętla while to właściwe narzędzie, gdy powtarzasz coś do zmiany warunku i nie masz wygodnego licznika. Gdy jednak chcesz po prostu odwiedzić każdy element kontenera (vector, tablicy, string) bez indeksu i bez warunku do pilnowania, nowoczesny C++ ma coś czytelniejszego: pętlę for po zakresie (for (auto x : container)). To temat następnej strony.
Najczęściej zadawane pytania
Jaka jest różnica między while a do-while w C++?
Pętla while sprawdza warunek przed pierwszym przebiegiem, więc jej ciało może nie wykonać się ani razu. Pętla do-while najpierw wykonuje ciało, a warunek sprawdza na końcu, więc zawsze wykonuje się co najmniej raz. Używaj do-while, gdy praca musi zostać wykonana, zanim da się zdecydować o powtórzeniu, na przykład przy pytaniu o dane wejściowe i ich późniejszej weryfikacji. Pamiętaj, że do-while wymaga średnika po zamykającym while (...).
Kiedy używać pętli while zamiast pętli for w C++?
Używaj pętli while, gdy nie masz wygodnego licznika i chcesz po prostu powtarzać coś, dopóki nie zmieni się jakiś warunek: czytać dane, aż cin zawiedzie, sprawdzać, aż wartość będzie gotowa, albo przetwarzać kolejkę, aż będzie pusta. Po pętlę for sięgaj, gdy znasz liczbę iteracji albo masz oczywisty indeks do liczenia.
Jak zatrzymać nieskończoną pętlę while w C++?
Upewnij się, że coś w ciele pętli w końcu sprawi, że warunek stanie się fałszywy (zmniejszenie licznika, przesunięcie wskaźnika, ustawienie flagi). W celowej pętli while (true) umieść w środku break chroniony przez if. Jeśli pętla się zawiesza, zwykle przyczyną jest brak aktualizacji zmiennej, od której zależy warunek.