break, continue i pętle zagnieżdżone

Pętla nie zawsze musi dobiec do końca. Czasem trzeba wyjść, gdy znajdziemy to, czego szukamy, albo pominąć jeden obieg i lecieć dalej. A gdy pętla znajdzie się wewnątrz innej pętli — trzeba dokładnie wiedzieć, której z nich dotyczy nasze polecenie.

C# break continue 50 min
CEL LEKCJI

Czego się dziś nauczysz

  • Przerwiesz pętlę w momencie znalezienia szukanej wartości
  • Pominiesz jeden obieg pętli bez przerywania całości
  • Wyjaśnisz, dlaczego break w switch wewnątrz pętli nie kończy pętli
  • Napiszesz pętlę w pętli i policzysz, ile razy wykona się blok wewnętrzny
  • Wyjdziesz z dwóch pętli naraz, nie używając goto
TEORIA

Instrukcja break

break natychmiast kończy pętlę. Program przeskakuje za jej klamrę zamykającą i leci dalej — pozostałe obiegi się nie wykonają.

Program.cs
for (int i = 1; i <= 10; i++)
{
    if (i == 4)
    {
        Console.WriteLine("Znalazlem czworke - koncze.");
        break;
    }

    Console.WriteLine($"Sprawdzam {i}");
}

Console.WriteLine("Po petli.");
wynik w konsoli
Sprawdzam 1
Sprawdzam 2
Sprawdzam 3
Znalazlem czworke - koncze.
Po petli.

Pętla miała wykonać dziesięć obiegów, wykonała cztery. Liczby od 5 do 10 nie zostały nawet sprawdzone.

Typowe zastosowanie: przerwij, gdy znalazłeś

To najczęstszy powód użycia break. Gdy szukasz jednej wartości, nie ma sensu przeglądać reszty:

Program.cs
string tekst = "programowanie";
char szukany = 'm';
int pozycja = -1;            // -1 oznacza "nie znaleziono"

for (int i = 0; i < tekst.Length; i++)
{
    if (tekst[i] == szukany)
    {
        pozycja = i;
        break;               // dalej szukac nie ma po co
    }
}

if (pozycja >= 0)
{
    Console.WriteLine($"Znak '{szukany}' pierwszy raz na pozycji {pozycja}.");
}
else
{
    Console.WriteLine($"Znaku '{szukany}' nie ma w tekscie.");
}
wynik w konsoli
Znak 'm' pierwszy raz na pozycji 6.

Dlaczego pozycja zaczyna od −1

Potrzebna jest wartość, która na pewno nie może być prawdziwym wynikiem. Pozycje w napisie numerujemy od zera, więc zero jest zajęte — jest poprawną odpowiedzią „pierwszy znak”. Minus jeden nigdy poprawną pozycją nie będzie, więc bezpiecznie oznacza „nie znaleziono”. Tak samo działa wbudowana metoda tekst.IndexOf(szukany).

TEORIA

Instrukcja continue

continue kończy bieżący obieg, ale nie pętlę. Program pomija resztę bloku i wraca na początek — do zwiększenia licznika i sprawdzenia warunku.

Program.cs
for (int i = 1; i <= 10; i++)
{
    if (i % 2 == 0)
    {
        continue;            // parzyste pomijamy
    }

    Console.WriteLine($"Nieparzysta: {i}");
}
wynik w konsoli
Nieparzysta: 1
Nieparzysta: 3
Nieparzysta: 5
Nieparzysta: 7
Nieparzysta: 9
InstrukcjaCo przerywaDokąd skacze
breakcałą pętlęza klamrę zamykającą pętli
continuetylko bieżący obiegdo warunku (w for — najpierw do części zwiększającej licznik)

continue w pętli while potrafi zawiesić program

W pętli for licznik zwiększa się zawsze, także przy continue — to część nagłówka pętli. W pętli while licznik zwiększasz ręcznie, więc continue postawione przed i++ pominie tę linię i pętla zatrzyma się na zawsze.

tak NIE wolno
int i = 1;

while (i <= 10)
{
    if (i % 2 == 0)
    {
        continue;            // i nigdy nie wzrosnie - petla nieskonczona
    }

    Console.WriteLine(i);
    i++;
}

W pętlach while bezpieczniej jest odwrócić warunek i nie używać continue w ogóle:

Program.cs
int i = 1;

while (i <= 10)
{
    if (i % 2 != 0)          // zamiast "pomijaj parzyste" - "wypisuj nieparzyste"
    {
        Console.WriteLine(i);
    }

    i++;
}

Kiedy continue naprawdę pomaga

Wtedy, gdy pozwala uniknąć głębokiego wcięcia. Zamiast owijać sto linii w if (dane_sa_dobre) { ... }, na początku pętli piszemy if (!dane_sa_dobre) { continue; } i reszta bloku zostaje na płaskim poziomie. Ten wzorzec nazywa się „wczesnym wyjściem” i naprawdę poprawia czytelność.

TEORIA

Pułapka: break w switch wewnątrz pętli

To jedno z najczęstszych nieporozumień u osób, które właśnie poznały switch. Instrukcja break odnosi się do najbliższej otaczającej konstrukcji — a jeśli jest nią switch, to pętla nawet nie drgnie.

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

    switch (p)
    {
        case "pomoc":
            Console.WriteLine("Dostepne: pomoc, koniec");
            break;                      // wychodzi ze switch
        case "koniec":
            break;                      // TEZ wychodzi tylko ze switch!
        default:
            Console.WriteLine("Nie znam takiego polecenia.");
            break;
    }
}

Program nigdy się nie zakończy: break w gałęzi „koniec” wychodzi ze switch, po czym pętla while (true) zaczyna kolejny obieg. Rozwiązanie — flaga sprawdzana przez pętlę:

Program.cs
bool dziala = true;

while (dziala)
{
    Console.Write("Polecenie: ");
    string p = (Console.ReadLine() ?? "").Trim().ToLower();

    switch (p)
    {
        case "pomoc":
            Console.WriteLine("Dostepne: pomoc, koniec");
            break;
        case "koniec":
            dziala = false;             // to zakonczy petle
            Console.WriteLine("Do zobaczenia.");
            break;
        default:
            Console.WriteLine("Nie znam takiego polecenia.");
            break;
    }
}

Warunek wraca na swoje miejsce

Przy okazji zniknęło while (true). Nagłówek pętli mówi teraz wprost, kiedy program działa — nie trzeba szukać w środku, gdzie jest wyjście. To zwykle lepszy zapis.

TEORIA

Pętle zagnieżdżone

Wewnątrz pętli może stać kolejna pętla. Zasada jest jedna i wystarczy ją raz zrozumieć: na każdy jeden obieg pętli zewnętrznej pętla wewnętrzna przebiega całą swoją drogę od początku do końca.

Program.cs
for (int i = 1; i <= 3; i++)
{
    Console.WriteLine($"Zewnetrzna: i = {i}");

    for (int j = 1; j <= 2; j++)
    {
        Console.WriteLine($"    wewnetrzna: j = {j}");
    }
}
wynik w konsoli
Zewnetrzna: i = 1
    wewnetrzna: j = 1
    wewnetrzna: j = 2
Zewnetrzna: i = 2
    wewnetrzna: j = 1
    wewnetrzna: j = 2
Zewnetrzna: i = 3
    wewnetrzna: j = 1
    wewnetrzna: j = 2

Pętla zewnętrzna wykonała się 3 razy, wewnętrzna — 3 × 2 = 6 razy. To mnożenie jest ważne: dwie pętle po 1000 obiegów to milion wykonań bloku wewnętrznego.

Klasyczne zastosowanie: tabela

Pętla zewnętrzna odpowiada za wiersze, wewnętrzna za kolumny wewnątrz wiersza. Kluczowa jest linia po pętli wewnętrznej — to ona kończy wiersz:

Program.cs
// naglowek
Console.Write("    ");
for (int j = 1; j <= 10; j++)
{
    Console.Write($"{j,5}");
}
Console.WriteLine();
Console.WriteLine(new string('-', 54));

// wiersze
for (int i = 1; i <= 10; i++)
{
    Console.Write($"{i,3}|");                 // etykieta wiersza, bez nowej linii

    for (int j = 1; j <= 10; j++)
    {
        Console.Write($"{i * j,5}");          // Write, nie WriteLine!
    }

    Console.WriteLine();                      // dopiero tu konczymy wiersz
}
wynik w konsoli
        1    2    3    4    5    6    7    8    9   10
------------------------------------------------------
  1|    1    2    3    4    5    6    7    8    9   10
  2|    2    4    6    8   10   12   14   16   18   20
  3|    3    6    9   12   15   18   21   24   27   30
  4|    4    8   12   16   20   24   28   32   36   40
  5|    5   10   15   20   25   30   35   40   45   50
  6|    6   12   18   24   30   36   42   48   54   60
  7|    7   14   21   28   35   42   49   56   63   70
  8|    8   16   24   32   40   48   56   64   72   80
  9|    9   18   27   36   45   54   63   72   81   90
 10|   10   20   30   40   50   60   70   80   90  100

Write wewnątrz, WriteLine na zewnątrz

Najczęstszy błąd przy tabelach: użycie Console.WriteLine w pętli wewnętrznej. Wtedy każda liczba ląduje w osobnej linii i zamiast tabeli dostajesz stumetrową kolumnę. Nowa linia należy do wiersza, więc jej miejsce jest po pętli wewnętrznej.

Gdy wewnętrzna pętla zależy od zewnętrznej

Warunek pętli wewnętrznej może korzystać ze zmiennej pętli zewnętrznej. Tak rysuje się trójkąty:

Program.cs
int wysokosc = 5;

for (int i = 1; i <= wysokosc; i++)
{
    for (int j = 1; j <= i; j++)      // uwaga: warunek to j <= i
    {
        Console.Write('*');
    }

    Console.WriteLine();
}
wynik w konsoli
*
**
***
****
*****

W pierwszym wierszu pętla wewnętrzna wykona się raz, w drugim dwa razy, i tak dalej. Ten sam efekt można osiągnąć jedną linią — Console.WriteLine(new string('*', i)); — i w prawdziwym programie tak właśnie byłoby lepiej. Wersja z pętlą służy do zrozumienia mechanizmu.

TEORIA

Wyjście z dwóch pętli naraz

break kończy tylko tę pętlę, w której stoi — najbliższą. Gdy szukamy czegoś w tabeli i chcemy przerwać całe przeszukiwanie, jedno break nie wystarczy:

to nie wystarczy
for (int i = 1; i <= 3; i++)
{
    for (int j = 1; j <= 3; j++)
    {
        if (i * j == 4)
        {
            Console.WriteLine($"Znalazlem: {i} x {j}");
            break;                 // wychodzi tylko z petli po j
        }
    }
    // ...i tu petla po i leci dalej
}

Najczytelniejsze rozwiązanie to flaga — zmienna logiczna sprawdzana także przez pętlę zewnętrzną:

Program.cs
bool znaleziono = false;

for (int i = 1; i <= 3 && !znaleziono; i++)
{
    for (int j = 1; j <= 3; j++)
    {
        if (i * j == 4)
        {
            Console.WriteLine($"Znalazlem: {i} x {j}");
            znaleziono = true;
            break;                 // konczy petle wewnetrzna
        }
    }
    // warunek zewnetrznej sprawdzi flage i tez sie zakonczy
}

if (!znaleziono)
{
    Console.WriteLine("Nie znaleziono.");
}
wynik w konsoli
Znalazlem: 2 x 2

Istnieje też goto — i lepiej z niego nie korzystać

C# ma instrukcję goto etykieta, którą dałoby się wyskoczyć z dowolnej liczby pętli. Działa, ale burzy porządek programu tak bardzo, że w praktyce jej nie używamy. Docelowym rozwiązaniem jest wydzielenie przeszukiwania do osobnej metody i zakończenie jej instrukcją return — pokażemy to w bloku o funkcjach.

TEORIA

Kiedy break pomaga, a kiedy szkodzi

Dobre użyciePodejrzane użycie
Szukasz pierwszego pasującego elementu i po znalezieniu nie masz już nic do roboty. Kilka instrukcji break w jednej pętli, w różnych miejscach — nie da się już powiedzieć, kiedy pętla się kończy.
Sprawdzasz warunek, którego nie da się zapisać w nagłówku pętli (bo zależy od danych z wnętrza obiegu). break tam, gdzie wystarczyłby warunek w nagłówku while — wtedy nagłówek kłamie o tym, jak długo pętla działa.
Przerywasz kosztowne obliczenia, gdy dalszy ciąg nie ma sensu (np. liczba okazała się złożona). break jako sposób na wyjście z sytuacji, w której program i tak nie wie, co robić — wtedy problem jest gdzie indziej.

Prosta zasada na start: jeden break w pętli to zwykle dobrze, trzy to znak, że pętlę trzeba przemyśleć od nowa.

METODY WBUDOWANE

Użyte w tej lekcji elementy

NazwaZwracaDziałanie
tekst[i]char Znak napisu o podanym numerze. Numery zaczynają się od zera, ostatni to tekst.Length - 1. Numer spoza zakresu przerywa program.
tekst.IndexOf(z)int Gotowa wersja przeszukiwania z tej lekcji: numer pierwszego wystąpienia znaku lub fragmentu, a -1, gdy nie ma. LastIndexOf szuka od końca.
Console.Write(x)nic Wypisuje bez przechodzenia do nowej linii — podstawa budowania wierszy tabeli w pętli wewnętrznej.
new string(znak, n)string Napis z n powtórzeń znaku. Często zastępuje całą pętlę wewnętrzną.
Math.Sqrt(x)double Pierwiastek kwadratowy. W teście pierwszości wyznacza granicę, do której trzeba sprawdzać dzielniki.
Environment.NewLinestring Znak (lub para znaków) końca linii właściwy dla systemu. Przydatny, gdy budujesz wielolinijkowy napis zamiast wypisywać go od razu.
PRZYKŁAD Z OMÓWIENIEM

Liczby pierwsze do 50

Liczba pierwsza to taka większa od 1, która dzieli się bez reszty tylko przez 1 i przez samą siebie. Sprawdzenie wymaga pętli w pętli — i jest wzorcowym przykładem, w którym break naprawdę się opłaca.

Program.cs
const int Granica = 50;

int znalezione = 0;

Console.WriteLine($"Liczby pierwsze do {Granica}:");
Console.WriteLine();

for (int n = 2; n <= Granica; n++)
{
    bool pierwsza = true;

    // szukamy dzielnika; wystarczy sprawdzac do pierwiastka z n
    for (int d = 2; d * d <= n; d++)
    {
        if (n % d == 0)
        {
            pierwsza = false;
            break;               // jeden dzielnik wystarczy, reszty nie sprawdzamy
        }
    }

    if (!pierwsza)
    {
        continue;                // n nie jest pierwsza - nastepna liczba
    }

    Console.Write($"{n,4}");
    znalezione++;

    if (znalezione % 10 == 0)    // po dziesieciu liczbach lamiemy wiersz
    {
        Console.WriteLine();
    }
}

Console.WriteLine();
Console.WriteLine();
Console.WriteLine($"Znaleziono {znalezione} liczb pierwszych.");
wynik w konsoli
Liczby pierwsze do 50:

   2   3   5   7  11  13  17  19  23  29
  31  37  41  43  47

Znaleziono 15 liczb pierwszych.

Omówienie

FragmentCo robi i dlaczego tak
bool pierwsza = true; Zakładamy niewinność. Pętla wewnętrzna szuka dowodu winy — jednego dzielnika. Gdy go nie znajdzie, flaga zostaje prawdziwa. To bardzo częsty wzorzec: „załóż, że tak, i szukaj kontrprzykładu”.
d * d <= n Wystarczy sprawdzać dzielniki do pierwiastka z n. Gdyby n miało dzielnik większy od pierwiastka, to drugi czynnik byłby mniejszy — i znaleźlibyśmy go wcześniej. Zapis d * d <= n jest lepszy niż d <= Math.Sqrt(n): liczy na liczbach całkowitych, bez zaokrągleń i bez wywoływania metody w każdym obiegu.
break po znalezieniu dzielnika Dla liczby 100 dzielnik znajdujemy przy d = 2. Bez break sprawdzalibyśmy jeszcze 3, 4, 5… aż do 10 — zupełnie bez sensu, bo odpowiedź już znamy.
continue po nieudanym teście Alternatywą byłoby owinięcie trzech kolejnych instrukcji w if (pierwsza) { ... }. Wersja z continue jest płytsza i czyta się ją jak listę kroków: „nie jest pierwsza? następna”.
Console.Write($"{n,4}") Write, nie WriteLine — wszystkie liczby jednego wiersza mają stać obok siebie. Szerokość 4 zapewnia równe kolumny także dla liczb dwucyfrowych.
znalezione % 10 == 0 Łamanie wiersza co dziesiątą liczbę — po dziesiątej, dwudziestej i tak dalej. Sprawdzamy licznik znalezionych, a nie n, bo to znalezione trafiają do wiersza.
dwie liczby 2 i 3 Warto prześledzić: dla n = 2 warunek 2 * 2 <= 2 jest od razu fałszywy, więc pętla wewnętrzna nie wykonuje się ani razu, flaga zostaje prawdziwa i dwójka trafia na listę. Dokładnie tak ma być — i to jest ta sytuacja, w której for wykonuje się zero razy.

Ile obiegów oszczędza break

Dla granicy 50 różnica jest niezauważalna. Ale dla granicy miliona pętla wewnętrzna bez break wykonałaby się kilkaset razy dla każdej liczby parzystej, zamiast raz. To już różnica między sekundą a kilkoma minutami.

CZĘSTE BŁĘDY

Na czym się najczęściej potykamy

BłądCo się dzieje i jak poprawić
break w switch ma zakończyć pętlę Kończy tylko switch. Użyj flagi sprawdzanej przez pętlę.
break ma wyjść z dwóch pętli Wychodzi z najbliższej. Potrzebna flaga w warunku pętli zewnętrznej albo wydzielenie kodu do metody.
continue przed i++ w while Pętla nieskończona — licznik nigdy nie rośnie. W while unikaj continue albo zwiększaj licznik na początku bloku.
WriteLine w pętli wewnętrznej tabeli Każda wartość w osobnej linii. Wewnątrz Write, a WriteLine dopiero po pętli wewnętrznej.
ta sama nazwa licznika w obu pętlach CS0136 — nie można zadeklarować i wewnątrz zasięgu, w którym i już istnieje. Używaj i, j, k albo nazw znaczących (wiersz, kolumna).
kod po break w tym samym bloku CS0162: Unreachable code detected — ostrzeżenie o instrukcjach, do których program nigdy nie dojdzie. Zwykle oznacza, że break stoi za wcześnie.
zerowanie flagi w złym miejscu bool pierwsza = true; musi być wewnątrz pętli zewnętrznej — flaga dotyczy jednej liczby. Postawiona przed pętlą zostałaby fałszywa po pierwszej liczbie złożonej i wszystkie kolejne uznano by za złożone.
niedoszacowanie liczby obiegów Dwie pętle po 10 000 obiegów to sto milionów wykonań bloku wewnętrznego. Zanim uruchomisz — pomnóż zakresy.
d <= n / 2 zamiast d * d <= n Działa poprawnie, ale robi znacznie więcej pracy. Dla liczby miliona: 500 000 sprawdzeń zamiast 1000.
ZADANIA

Zadania

ZAD 1Sumowanie do ujemnej★☆☆

Użytkownik podaje liczby. Sumuj je, ale gdy poda liczbę ujemną — przerwij i wypisz sumę bez niej. Maksymalnie dziesięć liczb.

pokaż rozwiązanie
double suma = 0;
int ile = 0;

for (int i = 1; i <= 10; i++)
{
    Console.Write($"Liczba {i}/10 (ujemna konczy): ");
    bool ok = double.TryParse(Console.ReadLine(), out double x);

    if (!ok)
    {
        Console.WriteLine("  To nie liczba - pomijam.");
        continue;
    }

    if (x < 0)
    {
        Console.WriteLine("  Liczba ujemna - koncze.");
        break;
    }

    suma += x;
    ile++;
}

Console.WriteLine();
Console.WriteLine($"Zsumowano {ile} liczb, suma = {suma:F2}");
wynik w konsoli
Liczba 1/10 (ujemna konczy): 10
Liczba 2/10 (ujemna konczy): 5,5
Liczba 3/10 (ujemna konczy): abc
  To nie liczba - pomijam.
Liczba 4/10 (ujemna konczy): 4
Liczba 5/10 (ujemna konczy): -1
  Liczba ujemna - koncze.

Zsumowano 3 liczb, suma = 19,50

Obie instrukcje w jednej pętli i każda robi coś innego: continue pomija błędny wpis (ale zużywa jedną z dziesięciu prób), break kończy zbieranie. Liczba ujemna nie trafia do sumy, bo break stoi przed dodawaniem.

10 + 5,5 + 4 = 19,50.

ZAD 2Pomijanie wielokrotności★☆☆

Wypisz liczby od 1 do 30, pomijając te podzielne przez 3 lub przez 5. Na końcu podaj, ile liczb zostało wypisanych i ile pominiętych.

pokaż rozwiązanie
int wypisane = 0;
int pominiete = 0;

for (int i = 1; i <= 30; i++)
{
    if (i % 3 == 0 || i % 5 == 0)
    {
        pominiete++;
        continue;
    }

    Console.Write($"{i,4}");
    wypisane++;
}

Console.WriteLine();
Console.WriteLine();
Console.WriteLine($"Wypisano:  {wypisane}");
Console.WriteLine($"Pominieto: {pominiete}");
wynik w konsoli
   1   2   4   7   8  11  13  14  16  17  19  22  23  26  28  29

Wypisano:  16
Pominieto: 14

Liczby podzielne przez 3: 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 — dziesięć. Przez 5: 5, 10, 15, 20, 25, 30 — sześć. Ale 15 i 30 są na obu listach, więc pominiętych jest 10 + 6 − 2 = 14, a wypisanych 30 − 14 = 16. To zasada włączeń i wyłączeń — warto ją znać, bo taki błąd w rachunku bywa trudny do wypatrzenia.

ZAD 3Choinka★★☆

Wczytaj wysokość i narysuj trójkąt wyśrodkowany z gwiazdek: w wierszu i ma być 2·i − 1 gwiazdek, poprzedzonych spacjami tak, żeby trójkąt był symetryczny.

pokaż rozwiązanie
Console.Write("Wysokosc (1-20): ");
bool ok = int.TryParse(Console.ReadLine(), out int h);

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

    for (int i = 1; i <= h; i++)
    {
        // spacje przed gwiazdkami
        for (int s = 1; s <= h - i; s++)
        {
            Console.Write(' ');
        }

        // gwiazdki
        for (int g = 1; g <= 2 * i - 1; g++)
        {
            Console.Write('*');
        }

        Console.WriteLine();
    }
}
wynik dla wysokości 5
    *
   ***
  *****
 *******
*********

Dwie pętle wewnętrzne, jedna po drugiej, w jednym obiegu zewnętrznej: najpierw spacje, potem gwiazdki, a nowa linia dopiero po obu. Liczba spacji maleje (h - i), liczba gwiazdek rośnie (2 * i - 1) i suma zawsze daje tę samą szerokość.

Sprawdź na wierszu trzecim przy wysokości 5: spacji 5 - 3 = 2, gwiazdek 2 · 3 − 1 = 5.

Krócej: Console.WriteLine(new string(' ', h - i) + new string('*', 2 * i - 1)); — jedna linia zamiast dziewięciu. Warto umieć obie wersje.

ZAD 4Menu z pętlą★★☆

Rozbuduj menu z lekcji 13 tak, żeby program wracał do niego po każdej operacji i kończył się dopiero po wybraniu opcji „koniec”. Policz, ile operacji wykonano.

pokaż rozwiązanie
bool dziala = true;
int operacje = 0;

while (dziala)
{
    Console.WriteLine();
    Console.WriteLine("=== MENU ===");
    Console.WriteLine("  d - dodaj    u - usun");
    Console.WriteLine("  w - wypisz   k - koniec");
    Console.Write("Wybor: ");

    string wpis = (Console.ReadLine() ?? "").Trim().ToLower();

    if (wpis.Length == 0)
    {
        Console.WriteLine("Nic nie wybrano.");
        continue;
    }

    switch (wpis[0])
    {
        case 'd':
            Console.WriteLine("-> Dodawanie pozycji");
            operacje++;
            break;
        case 'u':
            Console.WriteLine("-> Usuwanie pozycji");
            operacje++;
            break;
        case 'w':
            Console.WriteLine("-> Wypisywanie listy");
            operacje++;
            break;
        case 'k':
            dziala = false;
            break;
        default:
            Console.WriteLine($"Nieznana opcja: '{wpis[0]}'");
            break;
    }
}

Console.WriteLine();
Console.WriteLine($"Wykonano operacji: {operacje}. Do zobaczenia!");
wynik w konsoli (skrócony)
=== MENU ===
  d - dodaj    u - usun
  w - wypisz   k - koniec
Wybor: d
-> Dodawanie pozycji

=== MENU ===
  d - dodaj    u - usun
  w - wypisz   k - koniec
Wybor: K

Wykonano operacji: 1. Do zobaczenia!

To jest ta pułapka z tej lekcji. W gałęzi 'k' nie może stać break zamiast dziala = false;break wyszedłby tylko ze switch i menu wyświetliłoby się ponownie. Flaga jest jedynym prostym rozwiązaniem.

continue przy pustym wpisie jest bezpieczne, bo to pętla while bez licznika — nie ma czego pominąć. Gdyby pętla liczyła obiegi, trzeba by uważać.

Nieznana opcja nie zwiększa licznika operacji, bo nic się nie wydarzyło.

ZAD 5Trójki pitagorejskie★★☆

Znajdź wszystkie trójki liczb całkowitych a ≤ b ≤ c z zakresu 1–30, dla których a² + b² = c². Policz, ile ich jest i ile razy wykonał się najgłębszy blok.

pokaż rozwiązanie
const int Max = 30;

int trojki = 0;
int sprawdzen = 0;

for (int a = 1; a <= Max; a++)
{
    for (int b = a; b <= Max; b++)          // b zaczyna od a, wiec a <= b
    {
        for (int c = b; c <= Max; c++)      // c zaczyna od b, wiec b <= c
        {
            sprawdzen++;

            if (a * a + b * b == c * c)
            {
                Console.WriteLine($"{a,3}{b,4}{c,4}");
                trojki++;
                break;                      // dla tej pary (a,b) drugiego c nie bedzie
            }
        }
    }
}

Console.WriteLine();
Console.WriteLine($"Trojek:    {trojki}");
Console.WriteLine($"Sprawdzen: {sprawdzen}");
wynik w konsoli
  3   4   5
  5  12  13
  6   8  10
  7  24  25
  8  15  17
  9  12  15
 10  24  26
 12  16  20
 15  20  25
 18  24  30
 20  21  29

Trojek:    11
Sprawdzen: 4845

Rozpoczynanie b od a, a c od b to najważniejsza część tego rozwiązania. Gdyby wszystkie trzy pętle zaczynały od 1, program znalazłby trójkę (3, 4, 5) i osobno (4, 3, 5) — a to ta sama trójka. Wymuszenie porządku a ≤ b ≤ c w nagłówkach pętli jest krótsze i szybsze niż odsiewanie duplikatów później.

break w najgłębszej pętli działa, bo dla ustalonych a i b istnieje co najwyżej jedno c — dalsze sprawdzanie byłoby pewną stratą czasu.

Trzy pętle zagnieżdżone oznaczają liczbę sprawdzeń rosnącą z trzecią potęgą zakresu. Dla granicy 30 to około pięciu tysięcy, dla 300 byłoby ich już około pięciu milionów. Warto to sobie policzyć, zanim się uruchomi.

ZAD 6Rozkład na czynniki pierwsze★★★

Wczytaj liczbę całkowitą większą od 1 i rozłóż ją na czynniki pierwsze, wypisując je w postaci iloczynu, np. 360 = 2 * 2 * 2 * 3 * 3 * 5.

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

if (!ok || liczba < 2)
{
    Console.WriteLine("Podaj liczbe calkowita wieksza od 1.");
}
else
{
    int reszta = liczba;
    string wynik = "";
    int czynnikow = 0;

    for (int d = 2; d * d <= reszta; d++)
    {
        while (reszta % d == 0)          // ten sam dzielnik moze wystapic wielokrotnie
        {
            wynik += (czynnikow == 0 ? "" : " * ") + d;
            czynnikow++;
            reszta /= d;
        }
    }

    // co zostalo, jest juz liczba pierwsza (o ile nie jest jedynka)
    if (reszta > 1)
    {
        wynik += (czynnikow == 0 ? "" : " * ") + reszta;
        czynnikow++;
    }

    Console.WriteLine($"{liczba} = {wynik}");
    Console.WriteLine($"Liczba czynnikow: {czynnikow}");

    if (czynnikow == 1)
    {
        Console.WriteLine($"{liczba} jest liczba pierwsza.");
    }
}
wynik dla 360
Podaj liczbe wieksza od 1: 360
360 = 2 * 2 * 2 * 3 * 3 * 5
Liczba czynnikow: 6
wynik dla 97
Podaj liczbe wieksza od 1: 97
97 = 97
Liczba czynnikow: 1
97 jest liczba pierwsza.

Pętla while wewnątrz pętli for. Dzielnik 2 mieści się w 360 aż trzy razy, więc trzeba go zdejmować, dopóki się da. Zwykłe if zdjęłoby tylko jedną dwójkę.

Dlaczego znalezione dzielniki na pewno są pierwsze? Idziemy od najmniejszych. Zanim dojdziemy do 4, wszystkie dwójki zostały już zdjęte, więc 4 nie podzieli reszty. Ten sam mechanizm eliminuje 6, 8, 9 i każdą inną liczbę złożoną — nie trzeba żadnego dodatkowego testu pierwszości.

Sprawdzenie po pętli jest konieczne. Dla liczby 97 warunek d * d <= reszta kończy pętlę przy d = 10, a w reszta nadal siedzi 97. Bez tej linii program nie wypisałby nic. Ten sam przypadek zachodzi dla 2 · 97 = 194: po zdjęciu dwójki zostaje 97, którego pętla już nie dosięgnie.

Uwaga na warunek d * d <= reszta: porównujemy z resztą, która maleje w trakcie działania — nie z pierwotną liczbą. Dzięki temu pętla kończy się dużo wcześniej.

Operator warunkowy czynnikow == 0 ? "" : " * " dokleja separator przed każdym czynnikiem oprócz pierwszego. To standardowa sztuczka na budowanie list rozdzielanych przecinkiem; w bloku o tablicach zastąpi ją metoda string.Join.

PODSUMOWANIE

Co trzeba zapamiętać

  • break kończy całą pętlę, continue tylko bieżący obieg.
  • Obie instrukcje dotyczą najbliższej otaczającej pętli — break w switch kończy switch, nie pętlę.
  • Do wyjścia z dwóch pętli naraz służy flaga sprawdzana w warunku pętli zewnętrznej.
  • continue w pętli while potrafi pominąć zwiększenie licznika i zawiesić program; w for licznik rośnie zawsze.
  • W pętlach zagnieżdżonych liczba wykonań bloku wewnętrznego to iloczyn zakresów.
  • W tabelach: Write w pętli wewnętrznej, WriteLine po niej.
  • Flagę w rodzaju bool pierwsza = true; zeruj wewnątrz pętli zewnętrznej, nie przed nią.
  • W teście pierwszości sprawdzaj dzielniki do pierwiastka — warunkiem d * d <= n.
  • Jeden break w pętli to zwykle dobrze; trzy oznaczają, że pętlę trzeba przemyśleć.

Koniec bloku 3

Twoje programy potrafią już podejmować decyzje i powtarzać czynności — to komplet narzędzi sterowania przebiegiem. Brakuje jednego: wszystkie dane wciąż trzymasz w osobnych zmiennych. W następnym bloku poznasz tablice i wtedy pętle pokażą pełnię swoich możliwości.