Pętla for powtarza blok, dopóki warunek jest prawdziwy, a przygotowanie i krok zapisuje się w nagłówku pętli. To pętla do liczenia: stała liczba powtórzeń albo każdy indeks tablicy.
Wynik:
Lap 1 of 5
Lap 2 of 5
Lap 3 of 5
Lap 4 of 5
Lap 5 of 5
Jak wykonują się trzy części
for (initializer; condition; iterator) wykonuje się w stałej kolejności:
- Inicjalizator (
int i = 1) wykonuje się raz, przed wszystkim innym. - Sprawdzany jest warunek (
i <= 5). Jeśli jest fałszywy, pętla się kończy i ciało już się nie wykona. - Wykonuje się ciało.
- Wykonuje się iterator (
i++), po czym sterowanie wraca do kroku 2.
Ponieważ warunek jest sprawdzany przed każdą iteracją, pętla for może wykonać się zero razy: for (int i = 10; i < 5; i++) nigdy nie wchodzi do ciała. Powyższa pętla działa jak ta pętla while:
int i = 1;
while (i <= 5)
{
Console.WriteLine($"Lap {i} of 5");
i++;
}
Obie formy różnią się jednym szczegółem: continue w ciele for nadal uruchamia iterator, a w tej wersji z while przeskoczyłoby i++, więc pętla sprawdzałaby to samo i ponownie i mogłaby kręcić się bez końca. Zmienna zadeklarowana w inicjalizatorze istnieje tylko wewnątrz pętli. Jeśli potrzebujesz jej końcowej wartości po pętli, zadeklaruj ją przed pętlą: int i; for (i = 0; ...; i++) { }.
Odliczanie w dół i krok
Iterator może być dowolnym wyrażeniem. Zmniejszaj, aby odliczać w dół, dodawaj więcej niż jeden, aby iść krokiem, albo mnóż, aby uzyskać ciąg geometryczny:
Wynik:
3... 2... 1... liftoff
00 15 30 45
1 10 100 1000
Upewnij się, że iterator przybliża warunek do fałszu. for (int i = 0; i < 10; i--) oddala się od 10 i zatrzymuje się dopiero wtedy, gdy i przekręci się po dwóch miliardach iteracji.
Przechodzenie po tablicy przez indeks
Tablice mają Length, listy mają Count, a poprawne indeksy biegną od 0 do wartości o jeden mniejszej. Indeks daje coś, czego nie daje foreach: dostęp do pozycji, do sąsiadów i możliwość zapisu z powrotem.
Wynik:
#1: 17.99
#2: 4.50
#3: 38.25
#4: 7.43
item 1 vs item 0: down
item 2 vs item 1: up
item 3 vs item 2: down
Ostatnia pętla zaczyna od 1, bo porównuje każdy element z poprzednim. Tworzenie, sortowanie i kopiowanie samych tablic omawia strona o tablicach.
Kilka zmiennych pętli
Inicjalizator może zadeklarować kilka zmiennych tego samego typu, a iterator może aktualizować kilka naraz, oddzielonych przecinkami. Klasyczne zastosowanie to dwa indeksy idące ku sobie:
Wynik:
racecar is a palindrome: True
9, 1, 8, 3
W jednej deklaracji obie zmienne muszą mieć ten sam typ: int left = 0, right = 10 działa, int i = 0, string s = "" nie.
Pętle zagnieżdżone
Pętla w pętli przechodzi cały swój zakres raz na każdą iterację pętli zewnętrznej. Typowy przypadek to tabele i siatki:
Wynik:
1 2 3 4
2 4 6 8
3 6 9 12
4 8 12 16
Wewnętrzne ciało wykonuje się 16 razy (4 × 4). Zagnieżdżanie szybko mnoży pracę: dwie zagnieżdżone pętle po 10 000 elementów każda to 100 milionów iteracji. break w pętli wewnętrznej opuszcza tylko pętlę wewnętrzną; strona o break i continue pokazuje, jak wyjść z obu.
Pętle nieskończone: for(;;)
Wszystkie trzy części nagłówka są opcjonalne. Bez warunku pętla działa, dopóki coś w jej wnętrzu jej nie opuści:
Wynik:
Month 1: 70 left
Month 2: 40 left
Month 3: 10 left
Overdrawn in month 4: -20
for (;;) i while (true) kompilują się do tego samego; wybierz to, czego używa twój projekt.
Błędy o jeden
Najczęstszy błąd w for to warunek, który idzie o krok za daleko:
Wynik:
Ada
Grace
Linus
IndexOutOfRangeException at index 3
Tablica 3 elementów ma indeksy 0, 1 i 2, więc właściwy warunek to i < names.Length. Lustrzany błąd to odliczanie w dół z i > 0, które nigdy nie odwiedza indeksu 0. W List<T> ten sam błąd rzuca zamiast tego ArgumentOutOfRangeException.
Usuwanie elementów w trakcie pętli
Usuwanie z List<T> w pętli idącej do przodu przesuwa każdy dalszy element o jedną pozycję w dół, więc pętla pomija element tuż za każdym usuniętym:
Wynik:
forward: 5, 0, 3, 7
backward: 5, 3, 7
Pętla do przodu zostawiła jedno 0: po usunięciu indeksu 1 drugie zero przesunęło się na indeks 1, ale i++ przeszło już do indeksu 2. Pętla od tyłu tego unika, bo usunięcia przesuwają tylko elementy już odwiedzone. Alternatywa w jednej linii, stock.RemoveAll(x => x == 0), robi to samo. Pętla foreach w ogóle nie pozwala na usuwanie; rzuca InvalidOperationException.
Liczniki zmiennoprzecinkowe
Unikaj liczników pętli typu double sprawdzanych przez równość. 0.1 nie ma dokładnej reprezentacji binarnej, więc dziesięciokrotne dodanie tej wartości nie daje dokładnie 1.0:
for (double x = 0; x != 1.0; x += 0.1) // never ends: x skips past 1.0
{
}
Zamiast tego licz liczbą całkowitą i obliczaj z niej wartość: for (int i = 0; i <= 10; i++) { double x = i / 10.0; }.
for a foreach
| Potrzeba | Użyj |
|---|---|
| Odczytać każdy element po kolei | foreach |
Indeks albo sąsiedzi (i - 1, i + 1) | for |
| Przypisać wartość do elementu tablicy | for |
| Iść krokiem 2, iść od tyłu | for |
Usuwać z List w trakcie pętli | for, od tyłu |
Kolekcja bez indeksera (HashSet, Dictionary, wyniki LINQ) | foreach |
| Powtórzyć coś N razy, bez kolekcji | for |
Dla tablic obie pętle kompilują się do praktycznie tego samego kodu, więc wybieraj według czytelności, a nie szybkości.
Najczęściej zadawane pytania
Jaka jest składnia pętli for w C#?
for (initializer; condition; iterator) { body }, na przykład for (int i = 0; i < 5; i++) { ... }. Inicjalizator wykonuje się raz, warunek jest sprawdzany przed każdą iteracją, a iterator wykonuje się po każdej iteracji. Pętla kończy się, gdy warunek po raz pierwszy okaże się fałszywy.
Jak w C# przejść pętlą od tyłu?
Zacznij od ostatniego indeksu i zmniejszaj go: for (int i = items.Length - 1; i >= 0; i--). Zwróć uwagę na >= 0, a nie > 0, inaczej pierwszy element zostanie pominięty. Pętla od tyłu to też bezpieczny sposób usuwania elementów z List w trakcie iteracji.
Kiedy w C# użyć for zamiast foreach?
Użyj for, gdy potrzebujesz indeksu (żeby porównać sąsiadów, zapisać coś z powrotem do tablicy albo iść krokiem większym niż jeden), gdy liczysz zamiast przechodzić kolekcję albo gdy usuwasz elementy z listy. Użyj foreach, aby odczytać wszystkie elementy po kolei; jest krótsza i nie ma indeksu, w którym można się pomylić.
Jak napisać nieskończoną pętlę w C#?
for (;;) { ... } albo while (true) { ... }. Wszystkie trzy części nagłówka for są opcjonalne, a brak warunku oznacza prawdę. Wyjdź z pętli przez break albo return, gdy spełni się warunek wyjścia.
Dlaczego moja pętla for rzuca IndexOutOfRangeException?
Warunek pozwala indeksowi dojść do długości: i <= array.Length odwiedza array[array.Length], czyli pozycję za ostatnim elementem. Poprawne indeksy biegną od 0 do Length - 1, więc warunek powinien brzmieć i < array.Length (albo i < list.Count dla List, która zamiast tego rzuca ArgumentOutOfRangeException).