Стирание () элемента в векторе не работает - PullRequest
10 голосов
/ 15 апреля 2020

У меня есть вектор. Мне нужно удалить последние 3 элемента в нем. Описал эту логику c. Программа вылетает. В чем может быть ошибка?

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

Ответы [ 7 ]

11 голосов
/ 15 апреля 2020

Это неопределенное поведение для передачи итератора end() в перегрузку с 1 параметром erase(). Даже если это не так, erase() делает недействительными итераторы, которые находятся "в и после" указанного элемента, делая d недействительным после 1-й итерации l oop.

std::vector имеет 2- параметр erase() перегрузка, которая принимает диапазон элементов для удаления. Вам не нужно руководство l oop вообще:

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

Live Demo

9 голосов
/ 15 апреля 2020

Если в векторе есть хотя бы 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
3 голосов
/ 15 апреля 2020

Во-первых, X.end() не возвращает итератор к последнему элементу вектора, он скорее возвращает итератор к элементу после последнего элемента вектора, который является элементом, которым вектор фактически не принадлежит, вот почему, когда вы пытаетесь стереть его с помощью X.erase(d), происходит сбой программы.

Вместо этого, при условии, что вектор содержит как минимум 3 элемента, вы можете сделать следующее:

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

Какие вместо этого идет к третьему последнему элементу и стирает каждый элемент после этого, пока не достигнет X.end().

РЕДАКТИРОВАТЬ: просто чтобы уточнить, X.end() является LegacyRandomAccessIterator которая указана для допустимой - операции, которая возвращает другую LegacyRandomAccessIterator .

2 голосов
/ 15 апреля 2020

Комментарий (теперь удаленный) в вопросе гласил, что «для итератора нет оператора». Однако следующий код компилирует и работает в 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 голосов
/ 15 апреля 2020

Определение end() из cppreference :

Возвращает итератор, ссылающийся на элемент «конец конца» в векторном контейнере.

и чуть ниже:

Он не указывает ни на один элемент и, следовательно, не должен быть разыменован.

Другими словами, вектор не имеет элемента, на который указывает end (). Разыменовывая, что неэлемент через метод erase (), вы, возможно, изменяете память, которая не принадлежит вектору. Следовательно, с этого момента могут происходить ужасные вещи.

Обычное соглашение C ++ описывает интервалы как [низкий, высокий), с «низким» значением , включенным в интервал, и «Высокое» значение исключено из интервала.

2 голосов
/ 15 апреля 2020

Вы можете использовать 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, для работы с которой требуется classi c iterator. Мы получаем это от rit, звоня base. Но этот новый итератор будет указывать на следующий элемент из rit в прямом направлении.
  • Именно поэтому мы передвигаемся на rit перед вызовом base и erase

Также, если вы хотите узнать больше о reverse_iterator, я предлагаю посетить этот ответ .

1 голос
/ 15 апреля 2020

Этот оператор

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

имеет неопределенное поведение.

И этот оператор пытается удалить только элемент перед последним элементом

    else X.erase(d - i);

, поскольку у вас есть все oop только с двумя итерациями

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 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...