Menu

Pętla for w C: składnia, przykłady i częste błędy

Jak powtarzać kod pętlą for w C: trzyczęściowy nagłówek, liczenie w górę i w dół, przechodzenie po tablicach z trikiem sizeof, pętle zagnieżdżone, pętle nieskończone oraz błędy o jeden i błędy z typami bez znaku, na które trafia każdy.

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

Po co pętla for

if i switch wybierają gałąź i wykonują ją raz. Prawdziwe programy muszą robić rzeczy wielokrotnie: wypisać każdy wynik, zsumować listę liczb, narysować dziesięć wierszy siatki, sprawdzić każdy znak w stringu. Pętla for to podstawowe narzędzie C do powtarzania kodu określoną liczbę razy, z licznikiem pod twoją kontrolą.

Wszystko, czego potrzebuje pętla for, mieści się w jednym zwartym nagłówku, więc "ile razy i jak" widać w jednej linii.

Trzyczęściowy nagłówek

Nagłówek for ma trzy części oddzielone średnikami: inicjalizację, warunek i aktualizację.

for (inicjalizacja; warunek; aktualizacja) {
    // ciało: wykonuje się, dopóki warunek jest niezerowy
}

Wykonują się w określonej kolejności, a jej znajomość wyjaśnia każdą pętlę for, jaką kiedykolwiek przeczytasz:

  1. Inicjalizacja wykonuje się raz, przed wszystkim innym.
  2. Sprawdzany jest warunek. Jeśli jest zerem, pętla od razu się kończy.
  3. Wykonuje się ciało.
  4. Wykonuje się aktualizacja.
  5. Powrót do kroku 2.

Prześledź pętlę powyżej według tej listy: int i = 0 wykonuje się raz. 0 < 5 jest spełnione, więc ciało wypisuje i = 0, a potem i++ ustawia i na 1. Warunek jest sprawdzany ponownie i tak dalej. Gdy i osiągnie 5, test zawodzi, ciało zostaje pominięte i wypisuje się gotowe. Ciało wykonało się dokładnie pięć razy, a i przyjmowało wartości od 0 do 4.

Wynikają z tego bezpośrednio dwie rzeczy. Warunek jest sprawdzany przed pierwszym obiegiem, więc pętla, której warunek od początku jest fałszywy, wykonuje się zero razy: for (int i = 10; i < 5; i++) w ogóle nie wchodzi do ciała. A aktualizacja wykonuje się po ciele, więc przez całe ciało i ma jeszcze starą wartość.

Deklarowanie licznika w nagłówku (int i = 0) jest dozwolone od C99 i to właściwy domyślny wybór: i istnieje wtedy tylko wewnątrz pętli, więc nie może zderzyć się z niczym za nią, a każda pętla w funkcji może swobodnie używać tej samej nazwy.

Liczenie w górę, w dół i co kilka

Aktualizacja nie ogranicza się do i++. Licz w dół, przeskakuj o dowolną wartość albo podwajaj w każdym obiegu:

Dopasuj warunek do aktualizacji. Liczenie w górę idzie w parze z < albo <=; liczenie w dół z > albo >=. Pomylenie tej pary to sposób na napisanie pętli, która nigdy się nie kończy.

Zauważ, że druga pętla używa <= 10, bo 10 to wartość, którą chcemy uwzględnić, a pierwszy przykład używał < 5, bo 5 już nie. Ten wybór to miejsce, w którym rodzą się błędy o jeden, więc dokonuj go świadomie: i < n wykonuje się n razy, zaczynając od 0; i <= n wykonuje się n + 1 razy.

Przechodzenie po tablicy

Najczęstsze zastosowanie pętli liczącej to przechodzenie po tablicy według indeksu. Licznik służy jednocześnie za pozycję, którą czytasz.

Z tego przykładu warto wynieść dwie rzeczy.

Trik z sizeof. sizeof(scores) to rozmiar całej tablicy w bajtach, a sizeof(scores[0]) to rozmiar jednego elementu, więc ich iloraz to liczba elementów. Dostosowuje się automatycznie, gdy dodasz element, czego nie zrobi wpisane na sztywno 5. Haczyk (i to spory) polega na tym, że działa to tylko tam, gdzie sama tablica jest w zasięgu. Przekaż tablicę do funkcji, a zdegraduje się do wskaźnika, więc sizeof poda tam rozmiar wskaźnika. Wewnątrz funkcji zawsze przyjmuj długość jako osobny parametr.

Warunek to i < n, nigdy i <= n. Pięcioelementowa tablica ma poprawne indeksy od 0 do 4. Odczyt scores[5] to niezdefiniowane zachowanie: może wypisać śmieci, wysypać program albo pozornie działać, po cichu psując coś innego. C w ogóle nie sprawdza granic, więc to w całości twoja odpowiedzialność.

Przechodzenie po tablicy od tyłu to ta sama idea w odwrotnym kierunku:

Zacznij od n - 1 (ostatniego poprawnego indeksu) i zatrzymaj się na 0 włącznie, stąd >=, a nie >. Trzymaj tu i jako int ze znakiem; następna sekcja wyjaśnia dlaczego.

Pętle zagnieżdżone

Umieść jedną pętlę for w drugiej, żeby pracować z siatkami, tabelami albo każdą parą elementów. Wewnętrzna pętla wykonuje się w całości dla każdego pojedynczego kroku pętli zewnętrznej.

Zewnętrzna pętla ustala row; wewnętrzna przechodzi po każdym col w tym wierszu; printf("\n") za wewnętrzną pętlą kończy linię. Przenieś ten znak nowej linii do środka wewnętrznej pętli, a cała tabela zamieni się w jedną kolumnę. Warto to wypróbować, bo struktura zagnieżdżenia staje się wtedy namacalna.

Nadawaj licznikom różne nazwy. row/col albo i/j są w porządku; użycie i w obu przesłania zewnętrzny licznik i daje zdumiewające wyniki. I uważaj na koszt: pętla n-krotna wewnątrz pętli n-krotnej wykonuje ciało n * n razy, więc para pętli po 1000 iteracji to milion obiegów.

Oto zagnieżdżona pętla, która wykonuje prawdziwą pracę: trójkąt z gwiazdek, w którym limit wewnętrznej pętli zależy od zewnętrznego licznika:

break i continue

Dwa słowa kluczowe zmieniają przebieg w środku pętli. break natychmiast opuszcza pętlę; continue pomija resztę bieżącego obiegu i skacze do aktualizacji.

Pierwsza pętla zatrzymuje się w chwili, gdy znajdzie 7, i nigdy nie sprawdza pozostałych 93 wartości. Druga używa continue, żeby pominąć wypisywanie liczb parzystych; i++ z nagłówka nadal się wykonuje, więc pętla dalej postępuje. O obu instrukcjach dużo więcej znajdziesz w artykule o break i continue, łącznie z tym, jak wyjść z pętli zagnieżdżonej, czego pojedynczy break nie potrafi.

Pętle nieskończone i puste części

Wszystkie trzy części nagłówka są opcjonalne. Pomiń warunek, a będzie on traktowany jako zawsze prawdziwy:

for (;;) {
    /* działa w nieskończoność: wyjdź przez break albo return */
}

for (;;) to idiomatyczna nieskończona pętla w C, a średniki nadal są wymagane. To nie błąd, o ile coś w środku może wykonać break, return albo exit: pętle zdarzeń i pętle typu "pytaj, aż wejście będzie poprawne" pisze się właśnie tak.

Pominąć można też inne części. Jeśli licznik już istnieje, pomiń inicjalizację; jeśli ciało go zwiększa, pomiń aktualizację:

int i = 0;
for (; i < n; ) {
    /* ... */
    i += step;
}

To jest dozwolone, ale czyta się gorzej niż pętlę while, którą w rzeczywistości jest. Wybieraj for, gdy licznik, jego limit i krok razem należą do nagłówka, a pętlę while, gdy tak nie jest.

Operator przecinka w nagłówku

Inicjalizacja i aktualizacja mogą zawierać po kilka wyrażeń oddzielonych przecinkami, i tak prowadzi się dwa liczniki naraz:

int i = 0, j = len - 1 deklaruje oba liczniki, a i++, j-- przesuwa oba. To operator przecinka, który oblicza swoje argumenty po kolei. Używaj go do naprawdę równoległych liczników; wciskanie do nagłówka niepowiązanej pracy tylko ją ukrywa.

Częste pułapki

Garść pułapek odpowiada za większość błędów z pętlą for w C.

Błąd o jeden. i <= n w tablicy indeksowanej od zera czyta jeden element za końcem. Używaj i < n dla "n razy, zaczynając od 0".

Średnik po nagłówku. To się kompiluje i prawie nigdy nie jest tym, o co ci chodziło:

/* BUG: ciałem pętli jest pusta instrukcja; printf wykonuje się raz */
for (int i = 0; i < 5; i++);
{
    printf("%d\n", i);
}

; staje się ciałem, więc pętla kręci się pięć razy, nic nie robiąc, a nawiasy poniżej wykonują się raz jako zwykły blok. gcc -Wall ostrzega przed tym.

Liczniki bez znaku liczące w dół. To jest nieskończona pętla:

/* BUG: wartość bez znaku nigdy nie jest < 0 */
for (size_t i = n - 1; i >= 0; i--) {
    process(a[i]);
}

i >= 0 jest zawsze prawdziwe dla typu bez znaku. Gdy i dojdzie do 0, i-- przekręca się do ogromnej liczby dodatniej, a pętla idzie dalej w pamięć, która do niej nie należy. Do liczenia w dół używaj int ze znakiem albo zapisz warunek jako for (size_t i = n; i-- > 0; ), co zmniejsza i sprawdza w jednym kroku i poprawnie zatrzymuje się na zerze.

Modyfikowanie licznika w dwóch miejscach. Zmienianie i zarówno w ciele, jak i w nagłówku sprawia, że liczba iteracji staje się nieprzewidywalna. Wybierz jedno miejsce.

Liczniki zmiennoprzecinkowe. for (double x = 0.0; x != 1.0; x += 0.1) może się nigdy nie skończyć, bo 0.1 nie ma dokładnej reprezentacji dwójkowej, a suma przeskakuje 1.0, nie trafiając w nie. Iteruj po liczbie całkowitej i obliczaj wartość w środku:

for (int i = 0; i <= 10; i++) {
    double x = i / 10.0;
    /* ... */
}

Zmiana długości tablicy podczas przechodzenia po niej. Warunek i < n odczytuje n w każdym obiegu, więc jeśli ciało zmniejsza kolekcję, świadomie koryguj indeks, zamiast pozwalać, żeby jedno z drugim się rozjechało.

Najczęściej zadawane pytania

Jak napisać pętlę for w C?

Umieść w nagłówku trzy części oddzielone średnikami: inicjalizację, warunek i aktualizację: for (int i = 0; i < 5; i++) { printf("%d\n", i); }. To wykonuje ciało pięć razy, a i przyjmuje wartości od 0 do 4. Pętla kończy się, gdy tylko warunek jest fałszywy.

Jak przejść pętlą po tablicy w C?

Licz indeks od 0 do długości (bez niej): for (int i = 0; i < n; i++) { sum += a[i]; }. Dla tablicy zadeklarowanej w tej samej funkcji długość możesz obliczyć przez sizeof(a) / sizeof(a[0]); po przekazaniu tablicy do funkcji ten trik przestaje działać, więc przekazuj długość jako parametr.

Dlaczego moja pętla for w C wykonuje się o jeden raz za dużo?

To błąd o jeden (off-by-one). W tablicy indeksowanej od zera o rozmiarze n poprawne indeksy to od 0 do n - 1, więc i <= n wykonuje jeden dodatkowy obieg i czyta za końcem tablicy, co jest niezdefiniowanym zachowaniem. Bezpieczny domyślny wybór to i < n.

Czy zmienną pętli można zadeklarować wewnątrz for w C?

Tak, od C99: for (int i = 0; i < n; i++). Zmienna istnieje wtedy tylko wewnątrz pętli, i o to właśnie chodzi. Kompiluj z gcc -std=c99 albo nowszym standardem (współczesne GCC i clang domyślnie używają C17, więc to po prostu działa); w starym stylu C89 i deklarowało się przed pętlą.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ