Удалить элементы вектора внутри цикла - PullRequest
63 голосов
/ 25 декабря 2011

Я знаю, что на этот вопрос есть похожие вопросы, но мне не удалось найти путь к моему коду с их помощью.Я хочу просто удалить / удалить элемент вектора, проверив атрибут этого элемента внутри цикла.Как я могу это сделать?Я попробовал следующий код, но я получаю смутное сообщение об ошибке:

'operator =' функция недоступна в 'Player'.

 for (vector<Player>::iterator it = allPlayers.begin(); it != allPlayers.end(); it++)
 {
     if(it->getpMoney()<=0) 
         it = allPlayers.erase(it);
     else 
         ++it;
 }

Что мне делать?

Обновление: Считаете ли вы, что вопрос vector :: erase с указателем относится к той же проблеме?Нужен ли мне, следовательно, оператор присваивания?Зачем?

Ответы [ 7 ]

114 голосов
/ 25 декабря 2011

Вы не должны увеличивать it в цикле for:

for (vector<Player>::iterator it=allPlayers.begin(); 
                              it!=allPlayers.end(); 
                              /*it++*/) <----------- I commented it.
{

   if(it->getpMoney()<=0) 
      it = allPlayers.erase(it);
  else 
      ++it;
 }

Обратите внимание на закомментированную часть; it++ там не требуется, так как it увеличивается в течениесамо тело.

Что касается ошибки " 'operator =", функция недоступна в' Player ' ", это происходит из-за использования erase(), который внутренне использует operator= для перемещенияэлементы в векторе.Чтобы использовать erase(), объекты класса Player должны быть назначаемыми, что означает, что вам нужно реализовать operator= для Player класса.

В любом случае, вам следует избегать необработанного цикла 1 в максимально возможной степени и следует вместо этого использовать алгоритмы.В этом случае популярная Erase-Remove Idiom может упростить то, что вы делаете.

allPlayers.erase(
    std::remove_if(
        allPlayers.begin(), 
        allPlayers.end(),
        [](Player const & p) { return p.getpMoney() <= 0; }
    ), 
    allPlayers.end()
); 

1.Это один из лучших докладов Шона Родителя , которые я когда-либо наблюдал.

13 голосов
/ 30 апреля 2014
if(allPlayers.empty() == false) {
    for(int i = allPlayers.size() - 1; i >= 0; i--) {
        if(allPlayers.at(i).getpMoney() <= 0) {
            allPlayers.erase( allPlayers.begin() + i ); 
        }
    }
}

Это мой способ удаления элементов в векторе.Это легко понять и не нужно никаких уловок.

11 голосов
/ 25 декабря 2011

Забудьте о цикле и используйте стандартные или расширенные диапазоны.
Используя Boost.Range en Lambda, это будет выглядеть так:

boost::remove_if( allPlayers, bind(&Player::getpMoney, _1)<=0 );
5 голосов
/ 25 декабря 2011

Ваша конкретная проблема в том, что в вашем классе Player нет оператора присваивания.Вы должны сделать «Player» копируемым или подвижным, чтобы удалить его из вектора.Это связано с тем, что вектор должен быть непрерывным и, следовательно, должен переупорядочивать элементы, чтобы заполнить пробелы, созданные при удалении элементов.

Также:

Использовать алгоритм std

allPlayers.erase(std::remove_if(allPlayers.begin(), allPlayers.end(), [](const Player& player)
{
    return player.getpMoney() <= 0;
}), allPlayers.end());

или даже проще, если у вас есть повышение:

boost::remove_erase_if(allPlayers, [](const Player& player)
{
    return player.getpMoney() <= 0;
});

См. Ответ TimW, если у вас нет поддержки лямбда-выражений C ++ 11.

4 голосов
/ 02 декабря 2016

Или сделать цикл в обратном направлении.

for (vector<Player>::iterator it = allPlayers.end() - 1; it != allPlayers.begin() - 1; it--)
    if(it->getpMoney()<=0) 
        it = allPlayers.erase(it);
3 голосов
/ 08 февраля 2018

C ++ 11 представила новую коллекцию функций, которые будут здесь полезны.

allPlayers.erase(
    std::remove_if(allPlayers.begin(), allPlayers.end(),
        [](auto& x) {return x->getpMoney() <= 0;} ), 
    allPlayers.end()); 

И тогда вы получаете преимущество, заключающееся в том, что вам не нужно делать слишком много сдвига конечных элементов.

0 голосов
/ 14 сентября 2018

Поздний ответ, но поскольку он видел неэффективные варианты:

  1. std::remove или std::remove_if - путь.
  2. Если по какой-либо причине они недоступны илине может использоваться по какой-либо другой причине, делайте то, что они скрывают от вас.

Код для эффективного удаления элементов:

auto pos = container.begin();
for(auto i = container.begin(); i != container.end(); ++i)
{
    if(isKeepElement(*i)) // whatever condition...
    {
        *pos++ = *i; // will move, if move assignment is available...
    }
}
// well, std::remove(_if) stops here...
container.erase(pos, container.end());

Возможно, вам понадобится написать такой цикл явно, напримересли вам нужен сам итератор, чтобы определить, должен ли элемент быть удален (параметр условия должен принимать ссылку на элемент, помните?), например, из-за определенных отношений с преемником / предшественником (хотя, если это отношение является равенством, существуетstd::unique).

...