Das Löschen () eines Elements in einem Vektor funktioniert nicht

10

Ich habe einen Vektor. Ich muss die letzten 3 Elemente darin löschen. Beschrieb diese Logik. Das Programm stürzt ab. Was könnte der Fehler sein?

vector<float>::iterator d = X.end();
    for (size_t i = 1; i < 3; i++) {
        if (i == 1) X.erase(d);
        else X.erase(d - i);
    }
dbUser11
quelle
Der Mörder hier existiert dnicht wirklich. Es ist der kanarische Wert, der nur verwendet werden kann, um das Ende des Kanars zu finden vector. Sie können es nicht entfernen. Sobald Sie einen Iterator löschen, ist er verschwunden. Sie können es danach nicht sicher für irgendetwas verwenden, einschließlich d - i.
user4581301

Antworten:

9

Wenn der Vektor mindestens 3 Elemente enthält, ist es einfach, die letzten 3 Elemente zu löschen. Verwenden Sie einfach dreimal pop_back :

#include <vector>
#include <iostream>

int main() 
{
    std::vector<float> v = { 1, 2, 3, 4, 5 };
    for (int i = 0; i < 3 && !v.empty(); ++i)
       v.pop_back();

    for ( const auto &item : v ) std::cout << item << ' ';
        std::cout << '\n';
}

Ausgabe:

1 2
PaulMcKenzie
quelle
11

Es ist ein undefiniertes Verhalten , den end()Iterator an die 1-Parameter- erase()Überladung zu übergeben. Selbst wenn dies nicht der erase()Fall wäre, werden Iteratoren ungültig, die sich "an und nach" dem angegebenen Element befinden, und dnach der Iteration der ersten Schleife ungültig.

std::vectorhat eine 2-Parameter- erase()Überladung, die eine Reihe von zu entfernenden Elementen akzeptiert. Sie brauchen überhaupt keine manuelle Schleife:

if (X.size() >= 3)
    X.erase(X.end()-3, X.end());

Live-Demo

Remy Lebeau
quelle
3

Erstens wird X.end()kein Iterator an das letzte Element des Vektors zurückgegeben, sondern ein Iterator an das Element hinter dem letzten Element des Vektors. Dies ist ein Element, das der Vektor nicht besitzt. Deshalb versuchen Sie es Löschen Sie es mit X.erase(d)dem Programm stürzt ab.

Sofern der Vektor mindestens 3 Elemente enthält, können Sie stattdessen Folgendes tun:

X.erase( X.end() - 3, X.end() );

Was stattdessen zum drittletzten Element geht und jedes Element danach löscht, bis es dazu kommt X.end().

BEARBEITEN: Zur Verdeutlichung X.end()handelt es sich um einen LegacyRandomAccessIterator , für den eine gültige -Operation angegeben ist, die einen anderen LegacyRandomAccessIterator zurückgibt .

Nikko77
quelle
2

Die Definition von end()from cppreference lautet:

Gibt einen Iterator zurück, der auf das Past-the-End-Element im Vektorcontainer verweist.

und etwas darunter:

Es zeigt auf kein Element und darf daher nicht dereferenziert werden.

Mit anderen Worten, der Vektor hat kein Element, auf das end () zeigt. Indem Sie dieses Nicht-Element durch die erase () -Methode dereferenzieren, ändern Sie möglicherweise den Speicher, der nicht zum Vektor gehört. Daher können von da an hässliche Dinge passieren.

Es ist die übliche C ++ Konvention Intervalle als [low, high) zu beschreiben, mit dem „niedrigen“ Wert enthielt im Intervall und der „hohe“ Wert ausgeschlossen aus dem Intervall.

jpmarinier
quelle
2

Sie könnten verwenden reverse_iterator:

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    vector<float> X = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6};

    // start the iterator at the last element
    vector<float>::reverse_iterator rit = X.rbegin();

    // repeat 3 times
    for(size_t i = 0; i < 3; i++)
    {
        rit++;
        X.erase(rit.base());
    }

    // display all elements in vector X
    for(float &e: X)
        cout << e << '\n';

    return 0;
}

Es gibt einige Dinge zu erwähnen:

  • reverse_iterator ritbeginnt am letzten Element der vector X. Diese Position wird aufgerufen rbegin.
  • eraseerfordert Klassiker, um damit iteratorzu arbeiten. Wir bekommen das ritdurch einen Anruf base. Dieser neue Iterator zeigt jedoch ritin Vorwärtsrichtung auf das nächste Element .
  • Deshalb bringen wir das ritvor dem Anruf baseund voranerase

Auch wenn Sie mehr darüber erfahren möchten reverse_iterator, empfehle ich Ihnen , diese Antwort zu besuchen .

sanitizedUser
quelle
2

In einem Kommentar (jetzt gelöscht) in der Frage heißt es: "Es gibt keinen Operator für einen Iterator." Der folgende Code kompiliert und funktioniert jedoch in beiden MSVCund clang-clmit dem Standard entweder C++17oder C++14:

#include <iostream>
#include <vector>

int main()
{
    std::vector<float> X{ 1.1f, 2.2f, 3.3f, 4.4f, 5.5f, 6.6f };
    for (auto f : X) std::cout << f << ' '; std::cout << std::endl;
    std::vector<float>::iterator d = X.end();
    X.erase(d - 3, d);  // This strongly suggest that there IS a "-" operator for a vector iterator!
    for (auto f : X) std::cout << f << ' '; std::cout << std::endl;
    return 0;
}

Die Definition für operator-lautet wie folgt (in der <vector>Kopfzeile):

    _NODISCARD _Vector_iterator operator-(const difference_type _Off) const {
        _Vector_iterator _Tmp = *this;
        return _Tmp -= _Off;
    }

Ich bin jedoch sicherlich kein C ++ - Sprachanwalt, und es ist möglich, dass dies eine dieser "gefährlichen" Microsoft-Erweiterungen ist. Es würde mich sehr interessieren, ob dies auf anderen Plattformen / Compilern funktioniert.

Adrian Mole
quelle
2
Ich denke, es ist gültig, da die Iteratoren eines Vektors Direktzugriff sind und -für diese Arten von Iteratoren definiert sind.
PaulMcKenzie
@PaulMcKenzie In der Tat - der Clang Static Analyzer (der mit Standards ziemlich streng sein kann) gab keine Warnung darüber.
Adrian Mole
1
Selbst wenn operator-für die Iteratoren keine definiert wären , könnten Sie einfach std::advance()oder std::prev()stattdessen verwenden.
Remy Lebeau
1

Diese Aussage

    if (i == 1) X.erase(d);

hat undefiniertes Verhalten.

Und diese Anweisung versucht, nur das Element vor dem letzten Element zu entfernen

    else X.erase(d - i);

weil Sie eine Schleife mit nur zwei Iterationen haben

for (size_t i = 1; i < 3; i++) {

Sie benötigen so etwas wie das Folgende.

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

int main() 
{
    std::vector<float> v = { 1, 2, 3, 4, 5 };

    auto n = std::min<decltype( v.size() )>( v.size(), 3 ); 
    if ( n ) v.erase( std::prev( std::end( v ), n ), std::end( v ) );

    for ( const auto &item : v ) std::cout << item << ' ';
    std::cout << '\n';

    return 0;
}

Die Programmausgabe ist

1 2 
Vlad aus Moskau
quelle