Menu

Pętla while i do while w C#: składnia, wczytywanie danych i pułapki

Jak działają pętle while i do while w C#: warunek sprawdzany przed ciałem albo po nim, wczytywanie danych do wartości wartowniczej lub do końca wejścia, while (true) z break oraz błędy nieskończonej pętli, na które łatwo się nadziać.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Pętla while powtarza swoje ciało tak długo, jak warunek pozostaje prawdziwy. Używaj jej, gdy nie wiesz z góry, ile iteracji potrzebujesz: kręć się, aż saldo osiągnie cel, kolejka się opróżni albo skończą się dane wejściowe.

Wynik:

Year 1: 1070.00
Year 2: 1144.90
Year 3: 1225.04
Year 4: 1310.80
Year 5: 1402.55
Year 6: 1500.73
Goal reached after 6 years

Warunek jest sprawdzany przed każdą iteracją, także przed pierwszą. Gdyby savings już osiągało cel, ciało nie wykonałoby się ani razu, a program wypisałby "after 0 years". Pętla zależy od tego, czy ciało zmienia coś, co odczytuje warunek; tutaj savings rośnie co roku, aż savings < goal stanie się fałszem.

do while: co najmniej jedno wykonanie

Pętla do while przenosi test na koniec, więc ciało zawsze wykonuje się co najmniej raz:

Wynik:

Attempt 1: got 7
Attempt 2: got 4
Attempt 3: got 1
Done after 3 attempts

Pierwsza wartość code istnieje dopiero po jednym wykonaniu ciała, a to dokładnie sytuacja, do której pasuje do while. Dwa szczegóły składni często zaskakują:

  • Średnik po while (code != 1) jest obowiązkowy. Jego brak to błąd kompilacji.
  • Zmienne używane w warunku trzeba zadeklarować przed do. Zmienna zadeklarowana wewnątrz nawiasów klamrowych jest już poza zakresem, gdy wykonuje się warunek, więc do { int code = ...; } while (code != 1); się nie kompiluje.

Ta sama pętla zapisana przez while wymaga albo powielenia pierwszego wywołania przed pętlą, albo sztucznej wartości początkowej, która wymusi pierwsze przejście. do while wyraża wprost zasadę "spróbuj, a potem zdecyduj, czy próbować dalej".

Wczytywanie danych do wartości wartowniczej

Wczytywanie wierszy do słowa stopu albo do końca wejścia to podręcznikowa pętla while. Console.ReadLine() zwraca null, gdy danych już nie ma, więc sprawdzaj oba warunki:

Wynik:

skipping "abc": not a number
3 amounts, total 49.50

(line = Console.ReadLine()) != null przypisuje następny wiersz i sprawdza go w jednym wyrażeniu; nawiasy wokół przypisania są konieczne, bo != ma wyższy priorytet niż =. Pętla zatrzymuje się na done, więc 99 stojące dalej nigdy nie zostaje wczytane. decimal.TryParse odrzuca abc bez rzucania wyjątku, a continue przechodzi do następnego wiersza. Bez sprawdzenia null dojście do końca wejścia przekazywałoby null do TryParse w nieskończoność, bo ReadLine wciąż zwraca null.

do while pasuje do wzorca "zapytaj i sprawdź", w którym pytanie musi pojawić się co najmniej raz:

Wynik:

Enter your age:
"-4" is not a valid age
Enter your age:
"fifteen" is not a valid age
Enter your age:
Age accepted: 15

Pytanie jest wewnątrz pętli, więc powtarza się po każdej odrzuconej odpowiedzi, a age jest zadeklarowane przed do, żeby kod za pętlą mógł go użyć. Gdy przy klawiaturze siedzi użytkownik, każda odpowiedź pojawia się w konsoli po swoim pytaniu; przy przekierowanym wejściu, jak tutaj, widać tylko wyjście samego programu.

while (true) z break

Czasem naturalny punkt wyjścia leży w środku ciała, po części pracy, a przed resztą. Zapisz wtedy pętlę jako while (true) i wyjdź z niej przez break:

Wynik:

processing: resize photo
processing: send email
stop signal received
1 job(s) left in the queue

Dequeue zdejmuje następne zadanie z początku kolejki, a pętla kończy się, gdy tylko natrafi na znacznik stopu, więc ostatnie zadanie zostaje w kolejce. while (true) nie jest złą praktyką, gdy wyjście jest czytelne i leży blisko początku ciała. Staje się nią, gdy w długim ciele rozsianych jest kilka instrukcji break; wtedy łatwiej śledzić nazwany warunek typu bool albo metodę z return. Strona o break i continue omawia wychodzenie z zagnieżdżonych pętli.

Pętle, które nigdy się nie kończą

Niezamierzona nieskończona pętla ma jedną z trzech przyczyn.

Ciało nigdy nie zmienia warunku. Licznik nie jest zwiększany albo zwiększana jest niewłaściwa zmienna:

int i = 0;
while (i < 5)
{
    Console.WriteLine(i);   // i++ forgotten: prints 0 forever
}

Średnik po warunku. Średnik jest całym ciałem pętli, a blok pod nim wykonuje się dopiero po pętli (która nigdy się nie kończy):

while (retries < 3);        // warning CS0642: Possible mistaken empty statement
{
    retries++;
}

Warunek, który przeskakuje swój cel. Test !=, gdy zmienna zmienia się skokami, może przeskoczyć wartość zatrzymania. while (x != 10) x += 3; przechodzi przez 0, 3, 6, 9, 12 i mija 10 (trafia w 10 tylko przypadkiem, gdy int się przepełni i zawinie miliardy kroków później). Gdy krok nie wynosi 1, testuj przez < albo > zamiast !=. To samo dotyczy wartości double, które rzadko trafiają dokładnie w cel: while (x != 1.0) x += 0.1; nigdy się nie kończy.

Wybór między while, do while i for

SytuacjaPętla
Znana liczba iteracji albo indeksfor
Każdy element kolekcjiforeach
Powtarzanie, dopóki warunek jest spełniony, być może ani razuwhile
Ciało musi wykonać się raz, zanim warunek nabierze sensudo while
Test wyjścia w środku ciaławhile (true) z break

Każdą pętlę for można przepisać jako pętlę while i odwrotnie; wybór sprowadza się do tego, która z nich umieszcza logikę pętli tam, gdzie czytelnik się jej spodziewa. Gdy istnieją licznik, test i krok, for trzyma je w jednym wierszu. Gdy pętlą steruje stan zmieniany wewnątrz ciała, while mówi to wprost.

Najczęściej zadawane pytania

Czym różni się while od do while w C#?

Pętla while sprawdza warunek przed każdą iteracją, więc jej ciało może nie wykonać się ani razu. Pętla do { } while (condition); sprawdza go po każdej iteracji, więc jej ciało zawsze wykonuje się co najmniej raz. Użyj do while, gdy dopiero pierwsze przejście daje wartość, którą testuje warunek, na przykład przy wczytaniu danych i ich sprawdzeniu.

Jaka jest składnia pętli do while w C#?

do { body } while (condition);. Zwróć uwagę na średnik po nawiasie zamykającym: jest obowiązkowy, a jego brak to błąd kompilacji. Warunek może używać zmiennych przypisywanych w ciele, ale trzeba je zadeklarować przed do, bo nawiasy klamrowe ciała tworzą osobny zakres.

Jak napisać while (true) w C#?

while (true) { ... } powtarza się bez końca, dopóki coś w środku nie wyjdzie z pętli przez break, return albo wyjątek. To naturalna postać, gdy test wyjścia pasuje do środka ciała, na przykład przy wczytywaniu wiersza i zatrzymaniu się, gdy jest pusty.

Jak w C# wczytywać dane, dopóki użytkownik nie wpisze słowa albo wejście się nie skończy?

Zapętl Console.ReadLine(), które na końcu wejścia zwraca null: string line; while ((line = Console.ReadLine()) != null && line != "done") { ... }. Przypisanie wewnątrz warunku wczytuje następny wiersz i sprawdza go w jednym kroku.

Dlaczego moja pętla while w C# nigdy się nie kończy?

Zwykle ciało nigdy nie zmienia tego, co testuje warunek (zapomniane i++), albo zaraz po warunku stoi średnik: while (count < 10); to pętla z pustym ciałem, która kręci się w nieskończoność. Kompilator ostrzega przed drugim przypadkiem komunikatem CS0642, Possible mistaken empty statement.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ