„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.