programowanieProgramista C++

Co to są iteratory w C++ STL, jakie ich rodzaje istnieją i jakie niuanse ich użycia należy znać podczas programowania?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

Iteratory to obiekty, które pozwalają odwoływać się do elementów kontenerów STL podobnie jak wskaźniki. Oferują jednolity sposób dostępu do elementów dowolnych kontenerów (vector, list, map itp.). Istnieje kilka rodzajów iteratorów:

  • InputIterator — do odczytu sekwencji w przód.
  • OutputIterator — do zapisu wartości sekwencji.
  • ForwardIterator — do odczytu i zapisu w przód (żyje dłużej niż OutputIterator).
  • BidirectionalIterator — pozwala na poruszanie się zarówno w przód, jak i w tył.
  • RandomAccessIterator — wspiera dostęp losowy (np. std::vector::iterator).

Szczególną uwagę należy zwrócić na życie iteratorów:

  • Iteratory kontenerów mogą stać się nieważne (unieważnione) przy zmianie kontenera. Na przykład, wstawienie elementów do wektora może sprawić, że stare iteratory staną się nieważne.
  • Różne kontenery różnie zarządzają cyklem życia iteratorów. W std::list wstawienie lub usunięcie nie unieważnia innych iteratorów, w std::vector — prawie zawsze unieważniają.

Przykład:

std::vector<int> v = {1,2,3,4,5}; for (auto it = v.begin(); it != v.end(); ++it) { if (*it == 3) { // Usuwamy element o wartości 3 it = v.erase(it); // erase zwraca iterator na następny element --it; // w razie potrzeby skorygować iterator } }

Pytanie z pułapką

Pytanie: Przy wywołaniu std::vector::insert, czy iteratory stają się nieważne?
Częsty odpowiedź: Nie, tylko przy dodawaniu poza zakresem końca.
Poprawna odpowiedź: Wszystkie iteratory i odniesienia, które są równe lub następują za pozycją wstawienia, stają się nieważne, jeśli pojemność kontenera się zwiększa. Jeśli pojemność jest wystarczająca — nieważne stają się tylko iteratory w zasięgu po punkcie wstawienia.

Przykład:

std::vector<int> v = {1,2,3}; auto it = v.begin() + 1; v.insert(v.begin(), 0); // it tutaj może zostać unieważnione!

Przykłady rzeczywistych błędów z powodu braku znajomości niuansów tematu


Historia: W projekcie do iteracji po std::vector używano wskaźników i po push_back iteracja kontynuowała się po unieważnionych wskaźnikach, co prowadziło do awarii aplikacji.



Historia: Programista usuwał elementy std::list za pomocą erase w pętli for(auto it : list), nie używając zwracanego iteratora erase, co spowodowało, że iteracja przeskakiwała przez elementy i nie wszystkie potrzebne zostały usunięte.



Historia: W kodzie używano std::map, a po erase według klucza iterator pozostawał powiązany z usuniętym elementem (niezdefiniowane zachowanie) — prowadziło to do przypadkowych awarii przy dalszych odwołaniach.