Menu

Pętla for po zakresie w C++: składnia, auto i referencje

Pętla for po zakresie (range-based for) w C++ wyjaśniona: czysta iteracja po tablicach, wektorach, napisach i mapach, dlaczego warto używać auto& i const auto& oraz pułapki kopiowania i unieważnienia iteratorów.

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

„Dla każdego elementu zrób to”

Klasyczna pętla for z licznikiem świetnie się sprawdza, gdy masz indeks, który ją napędza. Zwykle jednak indeks cię nie obchodzi: chcesz tylko dotknąć każdego elementu kontenera. Pisanie w tym celu for (int i = 0; i < v.size(); i++) to szum, a i dzieli jeden błąd o jeden od czytania poza zakresem.

C++11 dodał właśnie do tego pętlę for po zakresie. Nazywasz zmienną, wskazujesz kontener, a pętla przechodzi za ciebie przez każdy element:

Bez indeksu, bez .size(), bez granic, które można pomylić. Czytaj to jako „dla każdego s w scores”. Działa na surowych tablicach, std::vector, std::string, std::map i wszystkim innym, co udostępnia begin() i end().

Niech auto wybierze typ

Jawne podanie typu elementu działa, ale jest kruche: zmień typ kontenera, a trzeba zmienić każdą pętlę. Połącz for po zakresie z auto, a kompilator wydedukuje typ elementu za ciebie:

Jest tu jednak ukryty koszt. Zwykłe auto name dedukuje string i przy każdym przejściu kopiuje element do name. Dla int to nic nie kosztuje; dla string albo dużej struktury to zbędna alokacja w każdej iteracji. Rozwiązaniem są referencje, i to jest kolejna rzecz do zrozumienia.

Modyfikacja w miejscu przez auto&

Jeśli piszesz auto x, dostajesz kopię, więc przypisanie do x zmienia kopię, a nie kontener. Uważaj na tę pułapkę:

Mnożenie po cichu nic nie robi, bo n to jednorazowa kopia. Aby naprawdę edytować elementy, weź je przez referencję, czyli auto&:

Jedno & stanowi całą różnicę między „patrz, ale nie dotykaj” a „edytuj w miejscu”. Jeśli kiedyś zastanawiasz się, dlaczego twoje zmiany znikają, to niemal zawsze jest ten powód.

Odczyt bez kopiowania: const auto&

Gdy musisz tylko czytać elementy, ale ich kopiowanie jest drogie, użyj const auto&. Referencja unika kopii, a const dokumentuje (i wymusza), że niczego nie zmienisz:

Dobra praktyczna zasada:

for (auto x : c)         // kopia:  tanie typy (int, char, wskaźniki)
for (auto& x : c)        // edycja: chcesz zmieniać elementy
for (const auto& x : c)  // odczyt: ciężkie typy, które tylko sprawdzasz

Przy odczycie domyślnie używaj const auto&, a przy zapisie auto&. Po zwykłe auto sięgaj tylko przy naprawdę małych typach, tanich w kopiowaniu.

Pętla po mapach i parach

for po zakresie na std::map daje ci dla każdego wpisu std::pair, z .first (kluczem) i .second (wartością). Od C++17 structured bindings pozwalają rozpakować tę parę do dwóch nazwanych zmiennych bezpośrednio w nagłówku pętli:

[name, age] jest o wiele czytelniejsze niż powtarzanie wszędzie entry.first i entry.second. Tu też zostaw const auto&: klucz wpisu map to string, więc kopiowanie każdej pary byłoby marnotrawstwem.

Pułapka: nie zmieniaj rozmiaru w trakcie pętli

Największa pułapka to zmiana rozmiaru kontenera, gdy for po zakresie po nim przechodzi. Wywołanie push_back, erase, insert albo clear może przealokować pamięć pod spodem i unieważnić wewnętrzne iteratory pętli. Wynikiem jest niezdefiniowane zachowanie, czyli awarie albo śmieci, a nie przyjazny komunikat o błędzie:

vector<int> v = {1, 2, 3};
for (int x : v) {
    v.push_back(x);   // NIEZDEFINIOWANE ZACHOWANIE: realokacja unieważnia zakres
}

Jeśli musisz dodawać lub usuwać elementy podczas przetwarzania, przejdź na pętlę for z indeksem albo iteratorem i sam pilnuj granic, albo zbuduj osobny kontener z wynikiem i podmień go na końcu. Dwie mniejsze pułapki z tej samej rodziny: nigdy nie wiąż for po zakresie z obiektem tymczasowym, który od razu umiera (for (auto x : makeVector()) jest w porządku, ale for (auto& x : someObj.getTempVector()) może wisieć), i pamiętaj, że for (auto& c : myString) pozwala zmieniać pojedyncze znaki w miejscu.

Dalej: funkcje

Pętla for po zakresie porządkuje iterację, a wybór między auto / auto& / const auto&, który właśnie poznajesz, przenosi się wprost na jedno z najważniejszych narzędzi w C++. Dalej zamkniemy logikę w funkcjach wielokrotnego użytku: nadamy kodowi nazwę, parametry i wartość zwracaną, żeby można go było wywołać z dowolnego miejsca zamiast się powtarzać.

Najczęściej zadawane pytania

Czym jest pętla for po zakresie w C++?

Pętla for po zakresie odwiedza każdy element kontenera (tablicy, vector, string, map itd.) bez ręcznego zarządzania indeksem czy iteratorem. Składnia to for (auto x : container) { ... }. Dodano ją w C++11 i jest najczystszym sposobem na powiedzenie „zrób to dla każdego elementu”.

Kiedy w pętli for po zakresie używać auto& zamiast auto?

Użyj auto& x, gdy chcesz modyfikować elementy w miejscu, a const auto& x, gdy tylko je czytasz, ale chcesz uniknąć kopiowania (ważne przy string, vector czy dużych obiektach). Zwykłe auto x robi kopię w każdej iteracji: to w porządku dla tanich typów takich jak int, w innych przypadkach to marnotrawstwo.

Czy można zmieniać rozmiar wektora w pętli for po zakresie w C++?

Nie. Wywołanie push_back, erase, insert albo clear na kontenerze, po którym iterujesz, unieważnia wewnętrzne iteratory pętli i jest niezdefiniowanym zachowaniem: może spowodować awarię albo po cichu uszkodzić dane. Jeśli musisz dodawać lub usuwać elementy w trakcie pętli, użyj zamiast tego pętli for z indeksem albo z iteratorem.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ