Pętle for i foreach — powtarzanie z planem
Organizujesz szkolny turniej. Zamiast ręcznie wypisywać numery czterech stanowisk, zlecasz to pętli — a potem zmieniasz jedną liczbę i masz ich czterdzieści.
Czego się dziś nauczysz
- Zapiszesz pętlę
forz inicjalizacją, warunkiem i krokiem. - Prześledzisz wartości licznika na kartce, zanim uruchomisz program.
- Ustawisz krok inny niż jeden i policzysz w dół.
- Przejdziesz po napisie pętlą
foreach. - Dobierzesz
foralboforeachdo konkretnego zadania.
Przygotowanie: lekcje 01–16. Przewidywany czas: 45–90 minut z zadaniami. Przykłady wymagają .NET 8 lub nowszego, z włączonymi ImplicitUsings i Nullable.
Trzy części zapisane w jednym nawiasie
Pętla while z poprzedniej lekcji rozrzuca sterowanie licznikiem po trzech miejscach: deklaracja stoi przed pętlą, warunek w nawiasie, zwiększanie gdzieś w ciele. Łatwo o zapomnienie tego ostatniego — i o pętlę bez końca.
for zbiera wszystkie trzy części w jednym nawiasie, rozdzielone średnikami:
for (int numer = 1; numer <= 4; numer++)
{
Console.WriteLine($"Stanowisko {numer}");
}
| Część | Zapis w przykładzie | Kiedy się wykonuje |
|---|---|---|
| inicjalizacja | int numer = 1 | raz, przed pierwszym sprawdzeniem warunku |
| warunek | numer <= 4 | przed każdym obiegiem, także przed pierwszym |
| krok | numer++ | po każdym obiegu, przed ponownym sprawdzeniem warunku |
Kolejność jest zawsze ta sama i warto ją umieć wyrecytować: inicjalizacja → warunek → ciało → krok → warunek → ciało → krok → … aż warunek będzie fałszywy.
Ta sama pętla zapisana przez while
Każdą pętlę for da się przepisać na while i odwrotnie — to ten sam mechanizm w innym zapisie. for wybieramy wtedy, gdy z góry wiadomo, ile obiegów ma być; while wtedy, gdy zależy to od danych, których jeszcze nie znamy.
// dokladnie to samo, tylko rozsypane
int numer = 1;
while (numer <= 4)
{
Console.WriteLine($"Stanowisko {numer}");
numer++; // latwo o tym zapomniec
}
Prześledź licznik na kartce
Zanim uruchomisz jakąkolwiek pętlę, przećwicz ją na kartce. Tabelka poniżej pokazuje, co dzieje się przy for (int i = 1; i <= 4; i++).
| Obieg | Wartość i przy sprawdzaniu | Warunek i <= 4 | Co się dzieje |
|---|---|---|---|
| 1 | 1 | prawda | wykonuje się ciało, potem i rośnie do 2 |
| 2 | 2 | prawda | ciało, i rośnie do 3 |
| 3 | 3 | prawda | ciało, i rośnie do 4 |
| 4 | 4 | prawda | ciało, i rośnie do 5 |
| — | 5 | fałsz | pętla się kończy, program idzie dalej |
Zwróć uwagę na ostatni wiersz: po zakończeniu pętli licznik ma wartość o jeden większą od ostatniej użytej w ciele. To nie jest błąd — tak właśnie działa warunek sprawdzany przed obiegiem.
Zero obiegów to poprawny wynik
Warunek sprawdzany jest także przed pierwszym obiegiem. Jeśli od razu jest fałszywy — na przykład w for (int i = 1; i <= 0; i++) — ciało nie wykona się ani razu, a program po prostu pójdzie dalej. Przy liczeniu sumy albo średniej trzeba pamiętać, co program ma wtedy wypisać.
Krok nie musi wynosić jeden
for (int i = 0; i <= 10; i += 2) // 0, 2, 4, 6, 8, 10
for (int i = 5; i >= 1; i--) // 5, 4, 3, 2, 1
for (int i = 1; i <= 100; i *= 2) // 1, 2, 4, 8, 16, 32, 64
Krok musi pasować do warunku
Zapis for (int i = 5; i <= 1; i--) nie wykona się ani razu, bo 5 nie jest mniejsze od 1. Za to for (int i = 5; i >= 1; i++) nie skończy się nigdy — licznik rośnie, a warunek pozostaje prawdziwy. Przy odliczaniu w dół sprawdź zawsze obie rzeczy naraz: znak warunku i kierunek kroku.
Gdzie żyje licznik
Zmienna zadeklarowana w nawiasie pętli istnieje tylko w tej pętli. Po jej zakończeniu znika.
for (int i = 1; i <= 3; i++)
{
Console.WriteLine(i);
}
// Console.WriteLine(i); // BLAD CS0103: nazwa 'i' nie istnieje w tym kontekscie
Gdy wartość licznika jest potrzebna po zakończeniu pętli, deklarujemy go wcześniej:
int i;
for (i = 1; i <= 3; i++)
{
Console.WriteLine(i);
}
Console.WriteLine($"Po petli i = {i}"); // Po petli i = 4
Nazwa licznika
Dla zwykłego licznika przyjęło się i, potem j i k przy pętlach zagnieżdżonych. To jeden z nielicznych przypadków, w których jednoliterowa nazwa jest dobrą praktyką — każdy programista wie, co oznacza. Gdy licznik ma znaczenie, na przykład numer stanowiska, lepiej nazwać go numer.
foreach — gdy indeks nie jest potrzebny
Czasem nie chodzi o liczenie, tylko o przejście po wszystkim, co jest w jakimś zbiorze. Wtedy pilnowanie indeksu to niepotrzebna robota — i kolejna okazja do pomyłki.
foreach (char litera in "KOT")
{
Console.WriteLine(litera);
}
Czytamy to tak: „dla każdej litery w napisie KOT”. Pętla sama pobiera kolejne znaki i sama kończy pracę po ostatnim. Nie ma warunku, nie ma kroku, nie ma szans na wyjście poza zakres.
for | foreach | |
|---|---|---|
| Znasz numer elementu | tak, to licznik | nie |
| Możesz pominąć co drugi | tak — krok i += 2 | nie |
| Możesz iść od końca | tak | nie |
| Ryzyko wyjścia poza zakres | jest | brak |
| Typowe użycie | generowanie liczb, przeglądanie po indeksie | przejście po wszystkich elementach po kolei |
Zmiennej iteracyjnej nie zmieniamy
Zapis litera = 'X'; wewnątrz foreach to błąd kompilacji CS1656. Zmienna iteracyjna służy wyłącznie do odczytu bieżącego elementu. Gdy chcesz coś zmieniać, potrzebna jest pętla for i indeks — albo budowanie nowej wartości obok.
Po czym jeszcze da się przejść
Na razie umiemy przejść foreach po napisie, bo napis to ciąg znaków. Prawdziwą siłę ta pętla pokaże przy tablicach z lekcji 23 i kolekcjach z bloku 7 — tam wypisanie wszystkich elementów to jedna linijka.
Przykład: plan turnieju
Wklej cały przykład do nowego projektu konsolowego jako Program.cs. Zanim uruchomisz — przewidź każdy wiersz wyniku.
Console.WriteLine("PLAN TURNIEJU");
Console.WriteLine(new string('=', 24));
// 1. numery stanowisk
for (int numer = 1; numer <= 4; numer++)
{
Console.WriteLine($"Stanowisko {numer,2}: wolne");
}
// 2. odliczanie do startu
Console.Write("Start za: ");
for (int sekunda = 5; sekunda >= 1; sekunda--)
{
Console.Write($"{sekunda}... ");
}
Console.WriteLine("teraz!");
// 3. suma punktow rund - licznik sluzy do liczenia, nie do wypisywania
int suma = 0;
for (int runda = 1; runda <= 5; runda++)
{
suma += runda * 10;
}
Console.WriteLine($"Punkty do zdobycia: {suma}");
// 4. foreach po napisie - bez indeksu
Console.Write("Haslo rundy: ");
foreach (char litera in "ROBOT")
{
Console.Write($"[{litera}]");
}
Console.WriteLine();
PLAN TURNIEJU
========================
Stanowisko 1: wolne
Stanowisko 2: wolne
Stanowisko 3: wolne
Stanowisko 4: wolne
Start za: 5... 4... 3... 2... 1... teraz!
Punkty do zdobycia: 150
Haslo rundy: [R][O][B][O][T]
Co dzieje się po kolei
- Pierwsza pętla generuje numery — nigdzie ich nie przechowujemy, powstają w chwili użycia.
- Druga liczy w dół: warunek
sekunda >= 1pasuje do krokusekunda--. Zamiana jednego z nich dałaby zero obiegów albo pętlę bez końca. - W trzeciej licznik nie trafia na ekran — służy wyłącznie do obliczeń. Suma
10 + 20 + 30 + 40 + 50daje 150. Console.WritebezLinenie przechodzi do nowego wiersza — dlatego odliczanie mieści się w jednej linii.- Zapis
{numer,2}to wyrównanie do dwóch znaków z lekcji 13. Dzięki niemu numery stoją w kolumnie. foreachprzechodzi po pięciu znakach napisuROBOT— literaOpojawia się dwa razy, bo występuje w napisie dwa razy.
Co robi każdy użyty element
| Element | Zwraca | Działanie i ograniczenia |
|---|---|---|
for (start; warunek; krok) | — | Pętla z licznikiem. Każdą z trzech części można pominąć; for (;;) to pętla nieskończona. |
foreach (typ x in zrodlo) | — | Przechodzi po wszystkich elementach po kolei. Zmiennej x nie wolno przypisać nowej wartości. |
i++ / i-- | wartość sprzed zmiany | Zwiększa lub zmniejsza o jeden. W kroku pętli wynik nie jest wykorzystywany, więc i++ i ++i działają tam tak samo. |
i += 2 | nowa wartość | Krok o dowolną wartość. Działa też dla mnożenia: i *= 2. |
Console.Write(x) | void | Wypisuje bez przejścia do nowego wiersza — przydatne, gdy pętla ma budować jedną linię. |
new string(znak, n) | string | Napis z n powtórzeń znaku. Wygodna linia oddzielająca bez pętli. |
{x,2} w interpolacji | tekst | Wyrównanie do szerokości 2 znaków (lekcja 13). Liczba ujemna wyrównuje do lewej. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
for (int i = 1; i <= 4; i++); | Średnik zaraz po nawiasie staje się ciałem pętli. Pętla wykonuje się cztery razy, nie robiąc nic, a blok w klamrach — raz. Kompilator nie protestuje. |
for (int i = 1; i < 4; i++) | Trzy obiegi, nie cztery. Przy starcie od 1 granicę zapisujemy przez <=, przy starcie od 0 — przez <. |
for (int i = 5; i >= 1; i++) | Pętla bez końca — krok oddala się od warunku. Przerwij program klawiszami Ctrl+C. |
Użycie i po zakończeniu pętli | CS0103 — licznik zadeklarowany w nawiasie już nie istnieje. Zadeklaruj go wcześniej, jeśli ma przetrwać. |
litera = 'X'; w foreach | CS1656 — zmiennej iteracyjnej nie wolno zmieniać. |
Zmiana licznika wewnątrz ciała for | Kompiluje się, ale pętla przestaje być przewidywalna. Licznikiem steruje wyłącznie krok w nagłówku. |
Zadania
Wypisz liczby od 5 do 1, każdą w osobnym wierszu, a pod nimi napis START. Użyj pętli for liczącej w dół. Wynik ma mieć sześć wierszy.
Policz pętlą for sumę liczb od 1 do 10 i wypisz ją razem ze średnią z dwoma miejscami po przecinku. Nie wpisuj gotowego wyniku. Sprawdź program także dla górnej granicy równej 0 i zapisz, co wtedy wypisuje.
Przejdź pętlą foreach po napisie ROBOT i wypisz każdą literę w osobnym wierszu, otoczoną nawiasami kwadratowymi. Potem zapisz to samo pętlą for po indeksach i porównaj oba zapisy.
Wczytaj liczbę od użytkownika i wypisz jej tabliczkę mnożenia od 1 do 10 w postaci 7 x 3 = 21. Wyrównaj kolumny tak, żeby znaki równości stały jeden pod drugim.
Wypisz w jednym wierszu liczby od 1 do 30 podzielne przez 3, a w drugim — wszystkie parzyste z tego zakresu. Użyj dwóch pętli o różnym kroku, bez sprawdzania reszty z dzielenia.
Wypisz choinkę o wysokości wczytanej od użytkownika: w wierszu numer i ma stać 2·i − 1 gwiazdek, poprzedzonych spacjami tak, żeby całość była symetryczna. Sprawdź program dla wysokości 1, 5 i 0. Podpowiedź: liczbę spacji da się wyliczyć z numeru wiersza i wysokości, a napis z powtórzeń zbudujesz przez new string.
Co trzeba zapamiętać
forzbiera w jednym nawiasie inicjalizację, warunek i krok — w tej kolejności.- Warunek sprawdzany jest przed każdym obiegiem, także przed pierwszym: pętla może wykonać się zero razy.
- Po zakończeniu pętli licznik ma wartość o jeden dalszą niż ostatnia użyta w ciele.
- Krok musi zbliżać licznik do granicy z warunku — inaczej dostajesz zero obiegów albo pętlę bez końca.
- Licznik zadeklarowany w nawiasie istnieje tylko wewnątrz pętli.
foreachprzechodzi po wszystkich elementach bez indeksu; zmiennej iteracyjnej nie wolno zmieniać.
Dokumentacja: Microsoft Learn — temat tej lekcji.