Vektor löschen Iterator

74

Ich habe diesen Code:

int main()
{
    vector<int> res;
    res.push_back(1);
    vector<int>::iterator it = res.begin();
    for( ; it != res.end(); it++)
    {
        it = res.erase(it);
        //if(it == res.end())
        //  return 0;
    }
}

"Ein Iterator mit wahlfreiem Zugriff, der auf die neue Position des Elements zeigt, das auf das letzte durch den Funktionsaufruf gelöschte Element folgt. Dies ist das Vektorende, wenn die Operation das letzte Element in der Sequenz gelöscht hat."

Dieser Code stürzt ab, aber wenn ich den if(it == res.end())Teil verwende und dann zurückkehre, funktioniert er. Woher? Cache der for-Schleifen-Cache res.end()so, dass der ungleiche Operator ausfällt?

hidayat
quelle
Ähnliche Frage: stackoverflow.com/questions/347441/…
Naveen
7
Da dies nur eine Vereinfachung des Codes ist, versuche ich nicht, alle Elemente im realen Code zu löschen
Freitag,

Antworten:

151

res.erase(it) Gibt immer den nächsten gültigen Iterator zurück, wenn Sie das letzte Element löschen, auf das es zeigt .end()

Am Ende der Schleife ++itwird immer aufgerufen, also erhöhen Sie, .end()was nicht erlaubt ist.

Das einfache Überprüfen auf .end()immer noch führt jedoch zu einem Fehler, da Sie bei jeder Iteration immer ein Element überspringen ( itwird durch die Rückkehr von .erase()und dann erneut durch die Schleife "inkrementiert" ).

Sie möchten wahrscheinlich etwas wie:

 while (it != res.end()) {
        it = res.erase(it);    
 }

um jedes Element zu löschen

(Der Vollständigkeit halber: Ich gehe davon aus, dass dies ein vereinfachtes Beispiel ist. Wenn Sie einfach möchten, dass jedes Element entfernt wird, ohne dass eine Operation ausgeführt werden muss (z. B. Löschen), sollten Sie einfach aufrufen. res.clear())

Wenn Sie Elemente nur bedingt löschen, möchten Sie wahrscheinlich so etwas

for ( ; it != res.end(); ) {
  if (condition) {
    it = res.erase(it);
  } else {
    ++it;
  }
}
Pieter
quelle
ok also es inkrementiert zuerst und nach dem inkrementieren vergleicht es
hidayat
1
Nein, hidayat; Ihr Code versucht, alle Elemente im Vektor einzeln zu löschen. Um dies zu tun, sollten Sie mit res.begin()dem Iterator beginnen und ihn dann niemals vorrücken, sondern den Iterator abrufen, der beim Löschen eines Elements zurückgegeben wird (dasselbe gilt für alle STL-Container). Das Inkrement selbst ist der Teil, der falsch ist.
Mephane
Im realen Code versuche ich nicht, alle Elemente zu löschen, aber danke, ich verstehe, was ich jetzt falsch gemacht habe
Freitag,
Hallo, ich mache das genauso, aber ich erhalte immer noch den Fehler "out_of_range". Kannst du mir bitte sagen warum?
DukeLover
@DukeLover Sie müssen eine iterator++Weile tun, es ist .end()irgendwo gleich , ohne irgendeinen Code zu sehen, der alles ist, was ich erraten kann. Wenn Sie es nicht herausfinden können, stellen Sie vielleicht eine Frage?
Pieter
29
for( ; it != res.end();)
{
    it = res.erase(it);
}

oder allgemeiner:

for( ; it != res.end();)
{
    if (smth)
        it = res.erase(it);
    else
        ++it;
}
Crazylammer
quelle
3
Warum nicht ein while?
Chamini2
@ chamini2 Eine whileSchleife wäre in diesem Fall äquivalent.
Glhrmv
3

Da die Methode im Vektor löschen, wird der nächste Iterator des übergebenen Iterators zurückgegeben.

Ich werde ein Beispiel geben, wie man ein Element im Vektor beim Iterieren entfernt.

void test_del_vector(){
    std::vector<int> vecInt{0, 1, 2, 3, 4, 5};

    //method 1
    for(auto it = vecInt.begin();it != vecInt.end();){
        if(*it % 2){// remove all the odds
            it = vecInt.erase(it); // note it will = next(it) after erase
        } else{
            ++it;
        }
    }

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

    // recreate vecInt, and use method 2
    vecInt = {0, 1, 2, 3, 4, 5};
    //method 2
    for(auto it=std::begin(vecInt);it!=std::end(vecInt);){
        if (*it % 2){
            it = vecInt.erase(it);
        }else{
            ++it;
        }
    }

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

    // recreate vecInt, and use method 3
    vecInt = {0, 1, 2, 3, 4, 5};
    //method 3
    vecInt.erase(std::remove_if(vecInt.begin(), vecInt.end(),
                 [](const int a){return a % 2;}),
                 vecInt.end());

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

}

Ausgabe aw unten:

024
024
024

Eine generiertere Methode:

template<class Container, class F>
void erase_where(Container& c, F&& f)
{
    c.erase(std::remove_if(c.begin(), c.end(),std::forward<F>(f)),
            c.end());
}

void test_del_vector(){
    std::vector<int> vecInt{0, 1, 2, 3, 4, 5};
    //method 4
    auto is_odd = [](int x){return x % 2;};
    erase_where(vecInt, is_odd);

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;    
}
Jayhello
quelle
Tolle Antwort. Dies funktionierte für meinen Anwendungsfall
Martin Fasani
1

Die it ++ - Anweisung wird am Ende des Blocks ausgeführt. Wenn Sie also das letzte Element löschen, versuchen Sie, den Iterator zu erhöhen, der auf eine leere Sammlung zeigt.

Patrice Bernassola
quelle
1

Mit modernem C ++ können Sie "std :: remove_if" und einen Lambda-Ausdruck verwenden.

Dieser Code entfernt "3" des Vektors

vector<int> vec {1,2,3,4,5,6};

vec.erase(std::remove_if(begin(vec),end(vec),[](int elem){return (elem == 3);}), end(vec));
Niki Dimitrov
quelle
0

Löschen Sie den Iterator nicht und erhöhen Sie ihn dann. Keine Notwendigkeit zu erhöhen, wenn Ihr Vektor eine ungerade (oder gerade, ich weiß nicht) Anzahl von Elementen hat, werden Sie das Ende des Vektors verpassen.

Benoit
quelle
0

Sie erhöhen itdas Schleifenausdruck der for-Schleife über das Ende des (leeren) Containers hinaus.

kbjorklu
quelle
-1

Folgendes scheint ebenfalls zu funktionieren:

for (vector<int>::iterator it = res.begin(); it != res.end(); it++)
{
  res.erase(it--);
}

Sie sind sich nicht sicher, ob dies ein Fehler ist?

Skippy le Grand Gourou
quelle
Während dieser Code die Frage beantworten kann, ist es besser zu erklären, was er tut, und einige Verweise darauf hinzuzufügen.
Hamid Pourjam
1
Ich bin mir über den obigen Code nicht sicher. Es gibt 3 Hauptprobleme, die ich sehe. Erstens melden Sie res.erase (it) itnach dem Entfernen nicht zurück. Sie dürfen es nicht ++ für die Iterator-Anweisung enthalten, während Sie Dinge entfernen. Daher sollten Sie eine bedingte Prüfung durchführen, um es zu löschen. Wenn die Bedingung fehlschlägt, müssen Sie mit next ( it++) iterieren . Obwohl ich mich frage, warum du hast it--? Verzeihung, aber warum dekrementieren Sie den Iterator überhaupt? Vielleicht stolpere ich, wenn das der Fall ist, entschuldige ich mich.
Volkan Güven
@VG Danke, ich denke, Ihr Kommentar befasst sich mit der Frage in der Antwort, macht sie daher lehrreich und vielleicht erwähnenswert. Ich fürchte, ich verstehe die Logik der it--beiden nicht mehr, seitdem ist zu viel Wasser unter der Brücke geflossen…
Skippy le Grand Gourou
@SkippyleGrandGourou Danke für die Antwort, ich habe nicht wirklich etwas gefunden, das dem obigen Dekrementstatus entspricht. Könnte dies der Fall sein, um einen Schritt nach dem Entfernen zurück zu iterieren? Vielleicht ist es identisch mit it = res.erase(it)? Obwohl ich das wirklich bezweifle. Hmmmm
Volkan Güven
@VG: Laut Pieters Antwort "gibt res.erase(it)immer den nächsten gültigen Iterator zurück". Ich denke it--und it++storniere, also löscht dieser Code nach meinem Verständnis immer wieder das (neue) erste Element. Perform it--scheint jedoch keine gute Idee zu sein, da es itjetzt das erste Element ist…
Skippy le Grand Gourou
-1
if(allPlayers.empty() == false) {
    for(int i = allPlayers.size() - 1; i >= 0; i--)
    {
        if(allPlayers.at(i).getpMoney() <= 0) 
            allPlayers.erase(allPlayers.at(i));
    }
}

Das funktioniert bei mir. Und Sie müssen nicht daran denken, dass Indizes bereits gelöscht wurden.

Dawoon Yi
quelle
Wie können Sie sagen, dass dies für Sie funktioniert? Das hast du nie getestet. Dies wird nicht einmal kompiliert. alllPlayers.at (i) gibt keinen Iterator zurück. Aber erase () erwartet einen Iterator.
Elmue
-1

Als Modifikation der Antwort von crazylammer verwende ich oft:

your_vector_type::iterator it;
for( it = res.start(); it != res.end();)
{
    your_vector_type::iterator curr = it++;
    if (something)
        res.erase(curr);
}

Dies hat den Vorteil, dass Sie nicht vergessen müssen, Ihren Iterator zu erhöhen, sodass er bei komplexer Logik weniger fehleranfällig ist. Innerhalb der Schleife ist curr niemals gleich res.end () und befindet sich beim nächsten Element, unabhängig davon, ob Sie es aus Ihrem Vektor löschen.

Joseph Petroske
quelle
Laut Spezifikation (c ++ 11) ist dies nicht cool. Jeder Iterator und jede Referenz nach dem Löschpunkt ist ungültig (23.3.6.5/3). Daher ist sie (die Sie vor dem Löschen erhöht haben) nach dem Löschen ungültig. Quelle: kera.name/articles/2011/06/iterator-invalidation-rules-c0x
Håkon Egset Harnes
Können Sie einen Verweis auf die offizielle Spezifikation finden, die das sagt? Ich glaube, dass die Website ungenau ist
Joseph Petroske
Der neueste offen verfügbare Arbeitsentwurf für den Standard, den ich finden konnte, ist für c ++ 11, open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf. Sie finden den Text an der Stelle, auf die ich verwiesen habe in meinem ursprünglichen Kommentar. 23.3.6.5/3 "Effekte: Ungültige Iteratoren und Referenzen am oder nach dem
Löschpunkt
@ HåkonEgsetHarnes, das ist ein Entwurf vor C ++ 11. Siehe stackoverflow.com/questions/81656/…
MM
Der Code in dieser Antwort ist falsch. Durch das Löschen eines Elements aus einem Vektor werden alle Iteratoren zum Zeitpunkt des Löschens und später (einschließlich it) ungültig . en.cppreference.com/w/cpp/container/vector/erase
MM