Стирання () елемента у векторі не працює


10

У мене вектор. Мені потрібно видалити останні 3 елементи в ньому. Описана ця логіка. Програма збоїв. Що може бути помилкою?

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);
    }

Вбивці тут dнасправді не існує. Це значення "канарного каналу", що минуло в кінці, можна використовувати лише для пошуку кінця vector. Ви не можете його видалити. Далі, як тільки ви стерте ітератор, він пропаде. Ви не можете його безпечно використовувати після цього ні для чого, в тому числі d - i.
користувач4581301

Відповіді:


9

Якщо у векторі принаймні 3 елементи, видалити останні 3 елементи просто - просто використовуйте pop_back 3 рази:

#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';
}

Вихід:

1 2

11

Не визначена поведінка передати end()ітератор на 1-параметричне erase()перевантаження. Навіть якщо цього не було, erase()недійсні ітератори, які є "під і після", вказаного елемента, роблячи dнедійсними після ітерації 1-го циклу.

std::vectorмає 2-параметричне erase()перевантаження, яке приймає низку елементів для видалення. Ручний цикл вам взагалі не потрібен:

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

Демонстраційна демонстрація


3

По-перше, X.end()не повертає ітератор до останнього елемента вектора, він швидше повертає ітератор до елемента, що минає останній елемент вектора, який є елементом, яким власне вектор не володіє, тому, коли ви намагаєтеся видалити його при X.erase(d)збоях програми.

Натомість за умови, що вектор містить щонайменше 3 елементи, ви можете зробити наступне:

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

Що замість цього переходить до третього останнього елемента і стирає кожен елемент після цього, поки не потрапить X.end().

EDIT: Просто для уточнення, X.end()це LegacyRandomAccessIterator, який вказано, щоб мати дійсну -операцію, яка повертає інший LegacyRandomAccessIterator .


2

Визначення end()від cppreference :

Повертає ітератор, що посилається на останній елемент в векторному контейнері.

і трохи нижче:

Він не вказує на будь-який елемент і, таким чином, не може бути відмежований.

Іншими словами, вектор не має елемента, на який вказує кінець (). За разименованія , що НЕ-елемент через метод стирання (), ви , можливо , змінюючи пам'ять , яка не належить до вектору. Звідси можуть відбуватися потворні речі.

Це звичайна умова C ++ для опису інтервалів як [низький, високий], із значенням "низьке", включеним у інтервал, а значення "високе" виключене з інтервалу.


2

Ви можете використовувати 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;
}

Можна згадати кілька речей:

  • reverse_iterator ritпочинається з останнього елемента vector X. Ця позиція називається rbegin.
  • eraseвимагає класичної iteratorроботи. Ми отримуємо це rit, зателефонувавши base. Але цей новий ітератор буде вказувати на наступний елемент з ritпрямого напрямку.
  • Ось чому ми просуваємо ritперед тим, як дзвонити baseіerase

Також якщо ви хочете дізнатися більше про це reverse_iterator, пропоную відвідати цю відповідь .


2

У коментарі (зараз видалено) у запитанні було зазначено, що "немає - оператора для ітератора". Тим НЕ менше, наступний код компілюється і працює в обох MSVCі clang-cl, зі стандартним набором або C++17або 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;
}

Визначення, яке надається для operator-, таке (у <vector>заголовку):

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

Однак я, безумовно, не є юристом на мові C ++, і можливо, що це одне з тих "небезпечних" розширень Microsoft. Мені б дуже цікаво дізнатися, чи працює це на інших платформах / компіляторах.


2
Я думаю, що це дійсно, оскільки ітератори вектора є випадковим доступом і -визначені для цих типів ітераторів.
PaulMcKenzie

@PaulMcKenzie Дійсно - статичний аналізатор клаксону (який може бути досить суворим зі стандартами) не попереджав про це.
Адріан Моль

1
Навіть якби не було operator-визначено ітераторів, ви можете просто використовувати std::advance()або std::prev()замість цього.
Ремі Лебо

1

Це твердження

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

має невизначену поведінку.

І це твердження намагається видалити лише елемент перед останнім елементом

    else X.erase(d - i);

тому що у вас є цикл із лише двома ітераціями

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

Вам потрібно щось подібне.

#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;
}

Вихід програми є

1 2 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.