Pętle while i do-while

Instrukcja warunkowa pozwala coś pominąć. Pętla pozwala coś powtórzyć — tyle razy, ile trzeba, i tyle razy, ile z góry nie wiadomo. To dzięki niej program może pytać o dane do skutku, liczyć sumę wprowadzanych liczb albo działać, dopóki użytkownik nie wybierze „koniec”.

C# while do-while 50 min
CEL LEKCJI

Czego się dziś nauczysz

  • Powtórzysz fragment kodu, dopóki warunek jest spełniony
  • Napiszesz pętlę, która pyta o dane tak długo, aż użytkownik wpisze poprawną wartość
  • Odróżnisz while od do-while i wybierzesz właściwą
  • Zsumujesz i policzysz nieznaną z góry liczbę wprowadzonych wartości
  • Rozpoznasz i naprawisz pętlę nieskończoną
TEORIA

Pętla while

Pętla while wygląda dokładnie jak if — z jedną różnicą: po wykonaniu bloku wraca i sprawdza warunek jeszcze raz. I jeszcze raz. Dopóki warunek jest prawdziwy.

składnia
while (warunek)
{
    // instrukcje powtarzane, dopoki warunek jest prawdziwy
}
Program.cs
int i = 1;

while (i <= 5)
{
    Console.WriteLine($"Powtorzenie numer {i}");
    i++;
}

Console.WriteLine("Koniec petli.");
wynik w konsoli
Powtorzenie numer 1
Powtorzenie numer 2
Powtorzenie numer 3
Powtorzenie numer 4
Powtorzenie numer 5
Koniec petli.

Co się dzieje krok po kroku

KrokWartość iWarunek i <= 5Działanie
11prawdawypisuje „1″, zwiększa i do 2
22prawdawypisuje „2″, zwiększa i do 3
33prawdawypisuje „3″, zwiększa i do 4
44prawdawypisuje „4″, zwiększa i do 5
55prawdawypisuje „5″, zwiększa i do 6
66fałszpętla się kończy, program idzie dalej

Warunek sprawdzany jest przed każdym obiegiem

Także przed pierwszym. Gdyby i startowało od 10, warunek 10 <= 5 byłby od razu fałszywy i blok nie wykonałby się ani razu. To ważna cecha while — potrafi wykonać się zero razy.

TEORIA

Trzy rzeczy, o których trzeba pamiętać

Każda poprawna pętla while ma trzy elementy. Brak któregokolwiek to gotowy błąd:

Program.cs
int i = 1;              // 1. przygotowanie - wartosc poczatkowa

while (i <= 5)          // 2. warunek - kiedy powtarzac
{
    Console.WriteLine(i);
    i++;                // 3. zmiana - to, co w koncu unieważni warunek
}
ElementGdy go brakujeObjaw
przygotowanie zmienna nie istnieje albo nie ma wartości błąd kompilacji CS0103 lub CS0165
warunek warunek jest od początku fałszywy pętla nie wykonuje się ani razu — łatwo tego nie zauważyć
zmiana nic nie zmienia wartości sprawdzanej w warunku pętla nieskończona — program się zawiesza

Zmiana musi dotyczyć zmiennej z warunku

To nie wystarczy, że w pętli „coś się dzieje”. Musi zmieniać się dokładnie ta wartość, którą sprawdza warunek. Zwiększanie zmiennej suma, gdy warunek patrzy na i, nie zakończy pętli nigdy.

TEORIA

Pętla nieskończona

Prędzej czy później napiszesz taką pętlę przez pomyłkę. Warto wiedzieć, jak wygląda i co wtedy zrobić.

trzy sposoby na zawieszenie programu
// 1. brak zmiany licznika
int i = 1;
while (i <= 5)
{
    Console.WriteLine(i);      // i nigdy nie rosnie
}

// 2. srednik po warunku
int j = 1;
while (j <= 5);                // pusta instrukcja - petla bez tresci
{
    Console.WriteLine(j);
    j++;
}

// 3. zmiana w zla strone
int k = 5;
while (k > 0)
{
    Console.WriteLine(k);
    k++;                       // rosnie, a warunek czeka na zejscie do zera
}

Jak przerwać zawieszony program

W oknie konsoli naciśnij Ctrl + C. W Visual Studio zatrzymaj debugowanie kwadratowym przyciskiem „Stop” (albo Shift + F5). Jeśli program wypisuje coś w pętli, konsola potrafi zapchać się tysiącami linii w sekundę — im szybciej zareagujesz, tym lepiej.

Pętla nieskończona bywa celowa

Program z menu ma działać, dopóki użytkownik nie wybierze wyjścia. Wtedy pisze się pętlę bez warunku końcowego i przerywa ją instrukcją break — poznasz ją dokładniej w lekcji 16:

Program.cs
while (true)
{
    Console.Write("Polecenie (koniec = zamknij): ");
    string polecenie = (Console.ReadLine() ?? "").Trim().ToLower();

    if (polecenie == "koniec")
    {
        break;              // jedyne wyjscie z tej petli
    }

    Console.WriteLine($"Wykonuje: {polecenie}");
}

Taka pętla jest bezpieczna, o ile widzisz instrukcję break i jesteś pewien, że da się do niej dojść. Jeśli warunek wyjścia da się zapisać wprost w nawiasie while, zrób to — czyta się to lepiej.

TEORIA

Pętla do-while

Różnica jest jedna, ale zmienia wszystko: warunek sprawdzany jest po wykonaniu bloku. Dzięki temu blok wykona się co najmniej raz, choćby warunek był od początku fałszywy.

składnia
do
{
    // instrukcje - wykonaja sie przynajmniej raz
}
while (warunek);        // UWAGA: sredniki na koncu
Program.cs
int i = 10;

Console.WriteLine("--- while ---");
while (i <= 5)
{
    Console.WriteLine($"while: {i}");
    i++;
}

int j = 10;

Console.WriteLine("--- do-while ---");
do
{
    Console.WriteLine($"do-while: {j}");
    j++;
}
while (j <= 5);
wynik w konsoli
--- while ---
--- do-while ---
do-while: 10

Pętla while nie wykonała się ani razu, bo warunek 10 <= 5 jest fałszywy od początku. Pętla do-while zdążyła wykonać się raz, zanim sprawdziła ten sam warunek.

Cechawhiledo-while
Warunek sprawdzanyprzed każdym obiegiempo każdym obiegu
Minimalna liczba obiegówzerojeden
Średnik na końcunie majest, po nawiasie warunku
Typowe zastosowanieprzetwarzanie danych, których może nie byćmenu, pytanie o dane — coś, co trzeba zrobić raz, zanim jest o co pytać

Jak wybrać

Zadaj sobie pytanie: czy jest sens wykonać ten blok choć raz, zanim cokolwiek sprawdzę? Przy pytaniu użytkownika o dane — tak, przecież najpierw trzeba zapytać. To do-while. Przy „wypisz pozostałe pozycje z listy” — niekoniecznie, lista może być pusta. To while.

TEORIA

Wzorzec: pytanie do skutku

To obiecany w lekcji 10 brakujący fragment. Do tej pory program przy błędnych danych po prostu się kończył. Teraz będzie pytał tak długo, aż dostanie to, czego potrzebuje.

Program.cs
int wiek;
bool poprawny;

do
{
    Console.Write("Podaj wiek (0-120): ");
    bool ok = int.TryParse(Console.ReadLine(), out wiek);

    poprawny = ok && wiek >= 0 && wiek <= 120;

    if (!poprawny)
    {
        Console.WriteLine("  To nie jest poprawny wiek. Sprobuj jeszcze raz.");
    }
}
while (!poprawny);

Console.WriteLine($"Dziekuje. Zapisany wiek: {wiek}");
wynik w konsoli
Podaj wiek (0-120): dwadziescia
  To nie jest poprawny wiek. Sprobuj jeszcze raz.
Podaj wiek (0-120): -5
  To nie jest poprawny wiek. Sprobuj jeszcze raz.
Podaj wiek (0-120): 17
Dziekuje. Zapisany wiek: 17

Dlaczego akurat tak

FragmentUzasadnienie
int wiek; przed pętlą Zmienna musi przeżyć zakończenie pętli, bo używamy jej w ostatniej linii. Zadeklarowana wewnątrz bloku znikłaby razem z klamrą.
out wiek, a nie out int wiek Zmienna już istnieje, więc nie deklarujemy jej ponownie. Zapis out int wiek wewnątrz pętli utworzyłby drugą, lokalną zmienną i pierwsza zostałaby pusta.
do-while, a nie while Zanim cokolwiek sprawdzimy, musimy najpierw zapytać. Wersja z while wymagałaby sztucznego ustawienia poprawny = false przed pętlą.
ok && wiek >= 0 && ... Skrócone obliczanie z lekcji 8: gdy ok jest fałszywe, reszta warunku nie jest nawet sprawdzana. To ważne, bo po nieudanej konwersji wiek zawiera zero, które przypadkiem mieści się w zakresie.
while (!poprawny); „Powtarzaj, dopóki nie jest poprawnie”. Zapis while (poprawny) byłby odwrotnością — pętla kręciłaby się tylko przy dobrych danych.

Zawsze daj drogę wyjścia

Pętla walidacji zapętla się, dopóki dane są złe. Jeśli program dostaje dane z pliku, który się skończył, Console.ReadLine() zwraca brak wartości — i pętla nigdy się nie skończy. W programach uruchamianych z klawiatury to nie problem, ale w poważniejszym kodzie warto ograniczyć liczbę prób albo pozwolić na wpisanie „anuluj”.

TEORIA

Wzorzec: licznik i sumator

Drugi wzorzec, który wraca w co drugim zadaniu: przed pętlą przygotowujemy zmienne na wynik, w pętli je uzupełniamy, po pętli wypisujemy.

Program.cs
int suma = 0;        // sumator zaczyna od ZERA
int ile = 0;         // licznik zaczyna od ZERA
int i = 1;

while (i <= 10)
{
    suma += i;       // to samo co: suma = suma + i
    ile++;
    i++;
}

double srednia = (double)suma / ile;

Console.WriteLine($"Suma liczb 1-10:  {suma}");
Console.WriteLine($"Ile skladnikow:   {ile}");
Console.WriteLine($"Srednia:          {srednia:F2}");
wynik w konsoli
Suma liczb 1-10:  55
Ile skladnikow:   10
Srednia:          5,50

Sumator zaczyna od zera, iloczyn od jedynki

Gdyby suma startowała od 1, wynik byłby zawyżony o jeden. Odwrotnie przy mnożeniu: zmienna na iloczyn musi zaczynać od 1, bo wszystko pomnożone przez zero daje zero. To najczęstszy błąd przy liczeniu silni.

Skąd (double) przed dzieleniem

suma i ile są typu int, więc suma / ile byłoby dzieleniem całkowitym — dla 55 i 10 dałoby 5, a nie 5,5. Rzutowanie jednej ze stron na double włącza dzielenie z resztą. To pułapka z lekcji 7 i wraca przy każdej średniej.

TEORIA

Wzorzec: wartownik

Wartownik (ang. sentinel) to umówiona wartość, która oznacza koniec danych. Użytkownik wprowadza liczby jedna po drugiej, a wpisanie zera (albo pustej linii, albo słowa „koniec”) kończy zbieranie.

Program.cs
Console.WriteLine("Podawaj liczby. Pusta linia konczy.");

double suma = 0;
int ile = 0;

Console.Write("Liczba: ");
string wpis = Console.ReadLine() ?? "";

while (wpis.Length > 0)
{
    if (double.TryParse(wpis, out double liczba))
    {
        suma += liczba;
        ile++;
    }
    else
    {
        Console.WriteLine("  Pomijam - to nie liczba.");
    }

    Console.Write("Liczba: ");
    wpis = Console.ReadLine() ?? "";
}

Console.WriteLine();

if (ile == 0)
{
    Console.WriteLine("Nie podano zadnej liczby.");
}
else
{
    Console.WriteLine($"Podano liczb: {ile}");
    Console.WriteLine($"Suma:         {suma:F2}");
    Console.WriteLine($"Srednia:      {suma / ile:F2}");
}
wynik w konsoli
Podawaj liczby. Pusta linia konczy.
Liczba: 12,5
Liczba: 7
Liczba: abc
  Pomijam - to nie liczba.
Liczba: 20,5
Liczba:

Podano liczb: 3
Suma:         40,00
Srednia:      13,33

Wczytanie pojawia się dwa razy — i to jest poprawne

Raz przed pętlą (żeby warunek miał co sprawdzić przy pierwszym obiegu) i raz na końcu bloku (żeby przy kolejnym sprawdzeniu warunek patrzył na nową wartość). Ten układ nazywa się „wczytaj z wyprzedzeniem”. Alternatywa to while (true) z instrukcją break w środku — poznasz ją w lekcji 16 i wtedy powtórzenie zniknie.

Licznik dzieli — sprawdź, czy nie jest zerem

Gdyby użytkownik od razu nacisnął Enter, ile zostałoby zerem, a suma / ile na typie double dałoby NaN — program nie zgłosiłby błędu, tylko wypisał bzdurę. Dlatego przed liczeniem średniej zawsze sprawdzamy licznik.

METODY WBUDOWANE

Użyte w tej lekcji elementy

NazwaZwracaDziałanie
i++ / i--liczba Zwiększa / zmniejsza zmienną o 1. W pętli while to najczęstsza „zmiana”, która doprowadza do zakończenia.
suma += xliczba Skrót od suma = suma + x. Analogicznie -=, *=, /=.
s.Lengthint Właściwość — liczba znaków napisu. s.Length > 0 to sprawdzenie „coś wpisano”. Uwaga: to nie metoda, więc bez nawiasów.
double.TryParse(s, out x)bool Znane z lekcji 9. Wywołane wprost w warunku if pozwala połączyć konwersję i sprawdzenie w jedną linię.
Console.ReadKey(true)ConsoleKeyInfo Czeka na jeden klawisz. Właściwość .Key daje wciśnięty klawisz, a .KeyChar — jego znak. Przydatne do menu bez Entera.
Math.Abs(x)liczba Wartość bezwzględna — w tej lekcji przy sprawdzaniu, czy liczba zmieściła się w tolerancji.
new string(znak, n)string Napis złożony z n powtórzeń znaku — wygodne do rysowania linii i słupków.
PRZYKŁAD Z OMÓWIENIEM

Kasa sklepowa

Program przyjmuje kolejne pozycje zakupów (cena i ilość), sumuje je, a na koniec przyjmuje zapłatę i wydaje resztę. Wszystkie trzy wzorce z tej lekcji naraz.

Program.cs
Console.WriteLine("=== KASA ===");
Console.WriteLine("Podaj cene pozycji. Zero konczy paragon.");
Console.WriteLine();

decimal suma = 0;
int pozycje = 0;

// --- 1. wartownik: zbieranie pozycji do wpisania zera ---
decimal cena;

do
{
    Console.Write($"Pozycja {pozycje + 1} - cena (0 = koniec): ");
    bool ok = decimal.TryParse(Console.ReadLine(), out cena);

    if (!ok || cena < 0)
    {
        Console.WriteLine("  Podaj nieujemna liczbe.");
        cena = -1;                       // wymuszamy kolejny obieg
    }
    else if (cena > 0)
    {
        suma += cena;
        pozycje++;
        Console.WriteLine($"  Dodano. Suma: {suma:F2} zl");
    }
}
while (cena != 0);

Console.WriteLine();

if (pozycje == 0)
{
    Console.WriteLine("Paragon pusty. Do widzenia.");
}
else
{
    Console.WriteLine(new string('-', 28));
    Console.WriteLine($"{"Pozycji:",-16}{pozycje,12}");
    Console.WriteLine($"{"DO ZAPLATY:",-16}{suma,12:F2}");
    Console.WriteLine(new string('-', 28));
    Console.WriteLine();

    // --- 2. walidacja: zaplata musi wystarczyc ---
    decimal zaplata;
    bool wystarczy;

    do
    {
        Console.Write("Otrzymano od klienta: ");
        bool ok = decimal.TryParse(Console.ReadLine(), out zaplata);

        wystarczy = ok && zaplata >= suma;

        if (!ok)
        {
            Console.WriteLine("  To nie jest kwota.");
        }
        else if (!wystarczy)
        {
            Console.WriteLine($"  Za malo o {suma - zaplata:F2} zl.");
        }
    }
    while (!wystarczy);

    Console.WriteLine();
    Console.WriteLine($"Reszta: {zaplata - suma:F2} zl");
}
wynik w konsoli
=== KASA ===
Podaj cene pozycji. Zero konczy paragon.

Pozycja 1 - cena (0 = koniec): 12,50
  Dodano. Suma: 12,50 zl
Pozycja 2 - cena (0 = koniec): 4,99
  Dodano. Suma: 17,49 zl
Pozycja 3 - cena (0 = koniec): 23,00
  Dodano. Suma: 40,49 zl
Pozycja 4 - cena (0 = koniec): 0

----------------------------
Pozycji:                   3
DO ZAPLATY:            40,49
----------------------------

Otrzymano od klienta: 20
  Za malo o 20,49 zl.
Otrzymano od klienta: 50

Reszta: 9,51 zl

Omówienie

FragmentCo robi i dlaczego tak
decimal cena; przed pętlą Warunek while (cena != 0) stoi poza blokiem, więc zmienna musi być zadeklarowana wcześniej. Deklaracja wewnątrz pętli dałaby CS0103.
cena = -1; po błędzie Sztuczka, która wygląda dziwnie, ale rozwiązuje realny problem: po nieudanym TryParse zmienna zawiera zero, a zero jest sygnałem końca. Bez tej linii wpisanie litery kończyłoby paragon. Ustawiamy wartość, która na pewno nie jest zerem — pętla robi kolejny obieg.
else if (cena > 0) Zero nie jest ani błędem, ani pozycją — to sygnał końca. Dlatego dodajemy do sumy tylko wartości dodatnie, a wyjście z pętli obsługuje sam warunek.
{pozycje + 1} w zachęcie Licznik pokazuje numer pozycji, którą użytkownik właśnie wpisuje, a nie liczbę już dodanych. Drobiazg, ale bez tego pierwsza pozycja miałaby numer 0.
while (!wystarczy) Druga pętla, ta sama konstrukcja co wzorzec walidacji. Warunek łączy dwa wymagania: kwota ma być liczbą i ma wystarczyć.
suma - zaplata w komunikacie Konkretna informacja, ile brakuje, zamiast ogólnego „za mało”. Przy danych 40,49 i 20,00 daje 20,49.
decimal, nie double Pieniądze. Przy double suma 12,50 + 4,99 + 23,00 mogłaby wyjść 40,489999999999995 i reszta różniłaby się o grosz.

Sprawdź sam

12,50 + 4,99 + 23,00 = 40,49. Zapłata 50,00 zł daje resztę 9,51 zł. Uruchom program i wpisz swoje ceny — potem policz na kartce i porównaj.

CZĘSTE BŁĘDY

Na czym się najczęściej potykamy

BłądCo się dzieje i jak poprawić
brak zmiany licznika Pętla nieskończona — program się zawiesza. Sprawdź, czy w bloku zmienia się dokładnie ta zmienna, którą bada warunek.
while (warunek); Średnik tworzy pustą instrukcję: pętla bez treści, czyli zawieszenie. Kompilator ostrzega (CS0642), ale program się zbuduje. Wyjątek: w do-while średnik po while (...) jest obowiązkowy.
brak średnika w do-while Odwrotna pomyłka — CS1002. Zapamiętaj: while bez średnika, do-while ze średnikiem.
out int wiek wewnątrz pętli Tworzy nową zmienną w każdym obiegu, a ta zadeklarowana przed pętlą zostaje pusta. Gdy zmienna już istnieje, pisz samo out wiek.
zmienna wyniku deklarowana w pętli Zerowanie sumy przy każdym obiegu — na końcu zostaje tylko ostatnia wartość. Sumator i licznik deklaruj przed pętlą.
iloczyn zaczynający od zera Wynik zawsze zero. Zmienna na iloczyn startuje od 1.
suma / ile na typach int Dzielenie całkowite obcina resztę: 55/10 daje 5. Rzutuj jedną stronę: (double)suma / ile.
dzielenie przez licznik równy zero Przy typie int program przerywa działanie (DivideByZeroException), przy double po cichu zwraca NaN. Zawsze sprawdź if (ile > 0).
zero jako wartownik przy TryParse Po nieudanej konwersji zmienna dostaje zero — czyli przypadkiem sygnał końca. Albo wybierz inny wartownik (pusta linia), albo po błędzie nadpisz zmienną wartością, która końca nie oznacza.
błąd o jeden i < 5 daje pięć obiegów (1–4 przy starcie od 1), i <= 5 daje pięć obiegów przy starcie od 1. Zanim uruchomisz, wypisz sobie pierwszy i ostatni obieg na kartce.
ZADANIA

Zadania

ZAD 1Odliczanie★☆☆

Wczytaj liczbę całkowitą dodatnią i odlicz od niej w dół do 1, a na końcu wypisz „Start!”. Każdą liczbę w osobnej linii.

pokaż rozwiązanie
Console.Write("Od ilu odliczac? ");
bool ok = int.TryParse(Console.ReadLine(), out int start);

if (!ok || start < 1)
{
    Console.WriteLine("Podaj liczbe calkowita dodatnia.");
}
else
{
    int i = start;

    while (i >= 1)
    {
        Console.WriteLine(i);
        i--;
    }

    Console.WriteLine("Start!");
}
wynik w konsoli
Od ilu odliczac? 5
5
4
3
2
1
Start!

Licznik idzie w dół, więc zmianą jest i--, a warunkiem i >= 1. Pomyłka i++ dałaby pętlę nieskończoną — warunek i >= 1 nigdy przestałby być prawdziwy.

Kopiujemy start do i, żeby zachować wartość wpisaną przez użytkownika. Pętla mogłaby modyfikować start bezpośrednio, ale wtedy po jej zakończeniu nie dałoby się już powiedzieć, od czego zaczynaliśmy.

ZAD 2Tabliczka mnożenia★☆☆

Wczytaj liczbę od 1 do 10 i wypisz jej tabliczkę mnożenia od 1 do 10, w równych kolumnach.

pokaż rozwiązanie
Console.Write("Podaj liczbe (1-10): ");
bool ok = int.TryParse(Console.ReadLine(), out int n);

if (!ok || n < 1 || n > 10)
{
    Console.WriteLine("Podaj liczbe calkowita z zakresu 1-10.");
}
else
{
    Console.WriteLine();

    int i = 1;

    while (i <= 10)
    {
        Console.WriteLine($"{n,3} x {i,2} = {n * i,4}");
        i++;
    }
}
wynik dla n = 7 (fragment)
  7 x  1 =    7
  7 x  2 =   14
  7 x  3 =   21
  7 x  4 =   28
...
  7 x 10 =   70

Wyrównanie z lekcji 11 sprawia, że znaki równości stoją w jednej kolumnie także wtedy, gdy wynik ma dwie albo trzy cyfry. Bez niego tabliczka rozjechałaby się przy przejściu z 9 na 10.

W następnej lekcji zapiszemy tę samą pętlę jako for — będzie o dwie linie krótsza.

ZAD 3Suma cyfr liczby★★☆

Wczytaj liczbę całkowitą dodatnią i policz sumę jej cyfr — bez zamieniania jej na tekst. Wskazówka: reszta z dzielenia przez 10 daje ostatnią cyfrę, a dzielenie całkowite przez 10 ją odcina.

pokaż rozwiązanie
Console.Write("Podaj liczbe calkowita dodatnia: ");
bool ok = int.TryParse(Console.ReadLine(), out int liczba);

if (!ok || liczba < 0)
{
    Console.WriteLine("Podaj liczbe calkowita nieujemna.");
}
else
{
    int reszta = liczba;
    int suma = 0;
    int cyfry = 0;

    do
    {
        int cyfra = reszta % 10;     // ostatnia cyfra
        suma += cyfra;
        cyfry++;
        reszta /= 10;                // odcinamy ja
    }
    while (reszta > 0);

    Console.WriteLine($"Liczba {liczba} ma {cyfry} cyfr, ich suma to {suma}.");
}
wynik w konsoli
Podaj liczbe calkowita dodatnia: 48317
Liczba 48317 ma 5 cyfr, ich suma to 23.

Dlaczego do-while, a nie while? Dla liczby 0 pętla while (reszta > 0) nie wykonałaby się ani razu i program stwierdziłby, że zero ma zero cyfr. do-while zawsze przetworzy przynajmniej jedną cyfrę — i to jest poprawne, bo zero to jedna cyfra.

Pracujemy na kopii reszta, bo oryginał jest potrzebny do wypisania komunikatu. Pętla niszczy tę liczbę, na której działa.

Prześledź na kartce dla 48317: 48317 % 10 = 7, potem 4831 % 10 = 1, potem 483 % 10 = 3, 48 % 10 = 8, 4 % 10 = 4. Suma: 7 + 1 + 3 + 8 + 4 = 23.

ZAD 4Zgadnij liczbę★★☆

Program losuje liczbę od 1 do 100, a użytkownik zgaduje. Po każdej próbie program podpowiada „za mało” albo „za dużo”. Na końcu wypisuje liczbę prób.

pokaż rozwiązanie
Random losowy = new Random();
int szukana = losowy.Next(1, 101);      // 1 wlacznie, 101 WYLACZNIE

int proby = 0;
int strzal;

Console.WriteLine("Zgadnij liczbe od 1 do 100.");

do
{
    Console.Write("Twoja propozycja: ");
    bool ok = int.TryParse(Console.ReadLine(), out strzal);

    if (!ok)
    {
        Console.WriteLine("  To nie jest liczba.");
        strzal = -1;                    // zeby petla sie nie skonczyla
        continue;
    }

    proby++;

    if (strzal < szukana)
    {
        Console.WriteLine("  Za malo.");
    }
    else if (strzal > szukana)
    {
        Console.WriteLine("  Za duzo.");
    }
}
while (strzal != szukana);

Console.WriteLine();
Console.WriteLine($"Trafiles! Liczba to {szukana}. Prob: {proby}.");
przykładowy przebieg
Zgadnij liczbe od 1 do 100.
Twoja propozycja: 50
  Za malo.
Twoja propozycja: 75
  Za duzo.
Twoja propozycja: 62
  Za malo.
Twoja propozycja: 68
  Za duzo.
Twoja propozycja: 65

Trafiles! Liczba to 65. Prob: 5.

new Random() tworzy generator liczb losowych, a Next(1, 101) zwraca liczbę od 1 do 100. Górna granica jest wyłączna — to bardzo częsta pomyłka, przez którą setka nigdy by nie wypadła.

Instrukcja continue przeskakuje do sprawdzenia warunku, pomijając resztę bloku — dzięki niej błędny wpis nie zwiększa licznika prób. Poznasz ją dokładniej w lekcji 16; tutaj potraktuj ją jako „wróć na początek pętli”.

Zwróć uwagę, że przy trafieniu żaden komunikat się nie wypisuje — obie gałęzie if dotyczą chybienia, a wygraną obsługuje warunek pętli.

Do samodzielnego przemyślenia: optymalna strategia to za każdym razem strzelać w środek pozostałego przedziału. Ile maksymalnie prób potrzeba dla zakresu 1–100?

ZAD 5Statystyka wprowadzonych liczb★★☆

Użytkownik podaje liczby, aż wpisze pustą linię. Wypisz: ile liczb podano, sumę, średnią, wartość najmniejszą i największą.

pokaż rozwiązanie
Console.WriteLine("Podawaj liczby. Pusta linia konczy.");
Console.WriteLine();

int ile = 0;
double suma = 0;
double najmniejsza = 0;
double najwieksza = 0;

Console.Write("Liczba: ");
string wpis = Console.ReadLine() ?? "";

while (wpis.Length > 0)
{
    if (!double.TryParse(wpis, out double liczba))
    {
        Console.WriteLine("  Pomijam - to nie liczba.");
    }
    else
    {
        suma += liczba;

        if (ile == 0)
        {
            // pierwsza liczba jest jednoczesnie najmniejsza i najwieksza
            najmniejsza = liczba;
            najwieksza = liczba;
        }
        else
        {
            if (liczba < najmniejsza) { najmniejsza = liczba; }
            if (liczba > najwieksza)  { najwieksza = liczba; }
        }

        ile++;
    }

    Console.Write("Liczba: ");
    wpis = Console.ReadLine() ?? "";
}

Console.WriteLine();

if (ile == 0)
{
    Console.WriteLine("Nie podano zadnej liczby.");
}
else
{
    Console.WriteLine($"{"Ile liczb:",-14}{ile,10}");
    Console.WriteLine($"{"Suma:",-14}{suma,10:F2}");
    Console.WriteLine($"{"Srednia:",-14}{suma / ile,10:F2}");
    Console.WriteLine($"{"Najmniejsza:",-14}{najmniejsza,10:F2}");
    Console.WriteLine($"{"Najwieksza:",-14}{najwieksza,10:F2}");
}
wynik w konsoli
Podawaj liczby. Pusta linia konczy.

Liczba: 12
Liczba: -3,5
Liczba: 40
Liczba: 7,25
Liczba:

Ile liczb:             4
Suma:              55,75
Srednia:           13,94
Najmniejsza:       -3,50
Najwieksza:        40,00

Najważniejszy fragment to obsługa pierwszej liczby. Gdyby najmniejsza startowała od zera, to dla samych liczb dodatnich program zawsze zwracałby zero jako minimum. Rozwiązanie: pierwszą wczytaną wartość wpisujemy jednocześnie jako minimum i maksimum, a dopiero kolejne porównujemy.

Alternatywa spotykana w podręcznikach to start od double.MaxValue i double.MinValue. Działa, ale jest mniej czytelna i przy pustej liście zostawia dziwaczne wartości.

Sprawdzenie: 12 + (−3,5) + 40 + 7,25 = 55,75, średnia 55,75 / 4 = 13,9375 → po zaokrągleniu 13,94.

ZAD 6Największy wspólny dzielnik★★★

Wczytaj dwie liczby całkowite dodatnie i policz ich największy wspólny dzielnik algorytmem Euklidesa: dopóki druga liczba nie jest zerem, zastępuj parę (a, b) parą (b, a % b). Wypisz też najmniejszą wspólną wielokrotność.

pokaż rozwiązanie
Console.Write("Pierwsza liczba: ");
bool ok1 = int.TryParse(Console.ReadLine(), out int a);

Console.Write("Druga liczba: ");
bool ok2 = int.TryParse(Console.ReadLine(), out int b);

if (!ok1 || !ok2 || a < 1 || b < 1)
{
    Console.WriteLine("Podaj dwie liczby calkowite dodatnie.");
}
else
{
    int x = a;
    int y = b;
    int krok = 0;

    Console.WriteLine();
    Console.WriteLine($"{"krok",6}{"x",8}{"y",8}{"x % y",8}");
    Console.WriteLine(new string('-', 30));

    while (y != 0)
    {
        krok++;
        Console.WriteLine($"{krok,6}{x,8}{y,8}{x % y,8}");

        int pomocnicza = x % y;
        x = y;
        y = pomocnicza;
    }

    // po zakonczeniu petli NWD siedzi w zmiennej x
    int nwd = x;
    long nww = (long)a / nwd * b;

    Console.WriteLine();
    Console.WriteLine($"NWD({a}, {b}) = {nwd}");
    Console.WriteLine($"NWW({a}, {b}) = {nww}");
}
wynik dla 48 i 18
Pierwsza liczba: 48
Druga liczba: 18

  krok       x       y   x % y
------------------------------
     1      48      18      12
     2      18      12       6
     3      12       6       0

NWD(48, 18) = 6
NWW(48, 18) = 144

Zmienna pomocnicza jest konieczna. Zapis x = y; y = x % y; byłby błędny — po pierwszym przypisaniu x ma już nową wartość i reszta wyliczyłaby się z niewłaściwej pary. Zamiana wartości dwóch zmiennych zawsze wymaga trzeciej (albo krotki: (x, y) = (y, x % y); — to zapis z bloku o C# nowoczesnym).

Pracujemy na kopiach x i y, bo a i b są potrzebne w komunikacie końcowym i do wyliczenia NWW.

Uwaga na kolejność w NWW. Wzór to a · b / NWD, ale zapisany jako a / nwd * b — najpierw dzielimy, potem mnożymy. Dzięki temu iloczyn a · b nigdy nie powstaje i nie ma szans przepełnić zakresu. Dzielenie jest bezpieczne, bo NWD z definicji dzieli a bez reszty.

Tabelka z krokami nie jest częścią algorytmu — dopisałem ją, żebyś zobaczył, co dzieje się w każdym obiegu. W gotowym programie zostawiłbyś samą pętlę.

PODSUMOWANIE

Co trzeba zapamiętać

  • while sprawdza warunek przed blokiem — może wykonać się zero razy.
  • do-while sprawdza po — wykona się co najmniej raz i kończy się średnikiem.
  • Każda pętla potrzebuje trzech rzeczy: wartości początkowej, warunku i zmiany, która ten warunek w końcu unieważni.
  • Brak zmiany albo średnik po while (...) to pętla nieskończona. Przerywasz ją przez Ctrl + C.
  • Do pytania o dane „do skutku” używaj do-while ze zmienną logiczną poprawny.
  • Sumator i licznik deklaruj przed pętlą; suma zaczyna od 0, iloczyn od 1.
  • Przed policzeniem średniej sprawdź, czy licznik nie jest zerem, i rzutuj na double.
  • Przy szukaniu minimum i maksimum pierwszą wartość przyjmij za oba naraz — nie startuj od zera.
  • Zero jako wartownik jest ryzykowne przy TryParse, bo nieudana konwersja też daje zero.