【发布时间】:2021-08-01 00:37:08
【问题描述】:
如何在迭代时从地图中删除?喜欢:
std::map<K, V> map;
for(auto i : map)
if(needs_removing(i))
// remove it from the map
如果我使用map.erase,它将使迭代器无效
【问题讨论】:
如何在迭代时从地图中删除?喜欢:
std::map<K, V> map;
for(auto i : map)
if(needs_removing(i))
// remove it from the map
如果我使用map.erase,它将使迭代器无效
【问题讨论】:
标准的关联容器擦除习语:
for (auto it = m.cbegin(); it != m.cend() /* not hoisted */; /* no increment */)
{
if (must_delete)
{
m.erase(it++); // or "it = m.erase(it)" since C++11
}
else
{
++it;
}
}
请注意,我们真的想要一个普通的for 循环,因为我们正在修改容器本身。基于范围的循环应该严格保留在我们只关心元素的情况下。 RBFL 的语法通过甚至不将容器暴露在循环体中来清楚地说明这一点。
编辑。 在 C++11 之前,您无法擦除 const-iterators。在那里你不得不说:
for (std::map<K,V>::iterator it = m.begin(); it != m.end(); ) { /* ... */ }
从容器中删除元素与元素的常量性并不矛盾。以此类推,delete p 始终是完全合法的,其中p 是指向常量的指针。常量不限制生命周期; C++ 中的 const 值仍然可以停止存在。
【讨论】:
for (int i = 0; i < v.size(); i++) 进行对比。这里我们必须在循环中说v[i],即我们必须明确提及容器。另一方面,RBFL 引入了可直接用作值的循环变量,因此循环内部不需要容器知识。这是 RBFL 用于不必了解容器的循环的预期用途的线索。擦除是完全相反的情况,它与容器有关。
it 以获取下一个有效的迭代器,然后然后 擦除旧的迭代器。反过来就不行了!
it = v.erase(it); 现在也适用于地图。也就是说,all 关联元素上的 erase() 现在返回下一个迭代器。因此,不再需要在 delete() 中需要后增量 ++ 的旧 kludge。这(如果为真)是一件好事,因为 kludge 依赖于函数调用内的覆盖后增量魔术,由新手维护人员“修复”以从函数调用中取出增量,或交换它到一个预增量“因为这只是一种风格”,等等。
if 和 else 块中调用it++? 在这些之后调用一次还不够吗?
很伤心,嗯?我通常这样做的方式是构建一个迭代器容器,而不是在遍历期间删除。然后遍历容器并使用 map.erase()
std::map<K,V> map;
std::list< std::map<K,V>::iterator > iteratorList;
for(auto i : map ){
if ( needs_removing(i)){
iteratorList.push_back(i);
}
}
for(auto i : iteratorList){
map.erase(*i)
}
【讨论】:
简而言之“如何在迭代时从地图中删除?”
来自 GCC 地图 impl(注意 GXX_EXPERIMENTAL_CXX0X):
#ifdef __GXX_EXPERIMENTAL_CXX0X__
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// DR 130. Associative erase should return an iterator.
/**
* @brief Erases an element from a %map.
* @param position An iterator pointing to the element to be erased.
* @return An iterator pointing to the element immediately following
* @a position prior to the element being erased. If no such
* element exists, end() is returned.
*
* This function erases an element, pointed to by the given
* iterator, from a %map. Note that this function only erases
* the element, and that if the element is itself a pointer,
* the pointed-to memory is not touched in any way. Managing
* the pointer is the user's responsibility.
*/
iterator
erase(iterator __position)
{ return _M_t.erase(__position); }
#else
/**
* @brief Erases an element from a %map.
* @param position An iterator pointing to the element to be erased.
*
* This function erases an element, pointed to by the given
* iterator, from a %map. Note that this function only erases
* the element, and that if the element is itself a pointer,
* the pointed-to memory is not touched in any way. Managing
* the pointer is the user's responsibility.
*/
void
erase(iterator __position)
{ _M_t.erase(__position); }
#endif
新旧风格示例:
#include <iostream>
#include <map>
#include <vector>
#include <algorithm>
using namespace std;
typedef map<int, int> t_myMap;
typedef vector<t_myMap::key_type> t_myVec;
int main() {
cout << "main() ENTRY" << endl;
t_myMap mi;
mi.insert(t_myMap::value_type(1,1));
mi.insert(t_myMap::value_type(2,1));
mi.insert(t_myMap::value_type(3,1));
mi.insert(t_myMap::value_type(4,1));
mi.insert(t_myMap::value_type(5,1));
mi.insert(t_myMap::value_type(6,1));
cout << "Init" << endl;
for(t_myMap::const_iterator i = mi.begin(); i != mi.end(); i++)
cout << '\t' << i->first << '-' << i->second << endl;
t_myVec markedForDeath;
for (t_myMap::const_iterator it = mi.begin(); it != mi.end() ; it++)
if (it->first > 2 && it->first < 5)
markedForDeath.push_back(it->first);
for(size_t i = 0; i < markedForDeath.size(); i++)
// old erase, returns void...
mi.erase(markedForDeath[i]);
cout << "after old style erase of 3 & 4.." << endl;
for(t_myMap::const_iterator i = mi.begin(); i != mi.end(); i++)
cout << '\t' << i->first << '-' << i->second << endl;
for (auto it = mi.begin(); it != mi.end(); ) {
if (it->first == 5)
// new erase() that returns iter..
it = mi.erase(it);
else
++it;
}
cout << "after new style erase of 5" << endl;
// new cend/cbegin and lambda..
for_each(mi.cbegin(), mi.cend(), [](t_myMap::const_reference it){cout << '\t' << it.first << '-' << it.second << endl;});
return 0;
}
打印:
main() ENTRY
Init
1-1
2-1
3-1
4-1
5-1
6-1
after old style erase of 3 & 4..
1-1
2-1
5-1
6-1
after new style erase of 5
1-1
2-1
6-1
Process returned 0 (0x0) execution time : 0.021 s
Press any key to continue.
【讨论】:
mi.erase(it++); 有什么问题?
if(mi.empty()) break;。
我个人更喜欢这种更清晰、更简单的模式,但代价是增加了一个变量:
for (auto it = m.cbegin(), next_it = it; it != m.cend(); it = next_it)
{
++next_it;
if (must_delete)
{
m.erase(it);
}
}
这种方法的优点:
it 和 next_it 的含义在整个迭代过程中保持不变,允许您轻松添加引用它们的附加语句,而不必担心它们是否会按预期工作(除了当然,删除后不能使用it)。【讨论】:
假设 C++11,这是一个单行循环体,如果这与您的编程风格一致:
using Map = std::map<K,V>;
Map map;
// Erase members that satisfy needs_removing(itr)
for (Map::const_iterator itr = map.cbegin() ; itr != map.cend() ; )
itr = needs_removing(itr) ? map.erase(itr) : std::next(itr);
其他几个小的风格变化:
Map::const_iterator),而不是使用 auto。using 用于模板类型,以使辅助类型 (Map::const_iterator) 更易于阅读/维护。【讨论】:
C++20 草案包含便利函数std::erase_if。
因此,您可以使用该功能将其作为一条线来完成。
std::map<K, V> map_obj;
//calls needs_removing for each element and erases it, if true was reuturned
std::erase_if(map_obj,needs_removing);
//if you need to pass only part of the key/value pair
std::erase_if(map_obj,[](auto& kv){return needs_removing(kv.first);});
【讨论】: