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-итераторы. Там вы должны сказать:
for(std::map<K,V>::iterator it = m.begin(); it != m.end();){/* ... */}
Стирание элемента из контейнера не противоречит его постоянству. По аналогии, это всегда было совершенно законноdelete p где pуказатель на константу. Константность не ограничивает продолжительность жизни; константные значения в C ++ могут прекратить существование.
"даже не выставляя контейнер внутри тела цикла" что ты имеешь в виду?
Дани
2
@ Дани: Ну, противопоставьте это строительству 20-го века for (int i = 0; i < v.size(); i++). Здесь мы должны сказать v[i]внутри цикла, то есть мы должны явно упомянуть контейнер. RBFL, с другой стороны, представляет переменную цикла, которая непосредственно используется в качестве значения, и поэтому знание цикла внутри цикла не требуется. Это ключ к предполагаемому использованию RBFL для циклов, которые не должны знать о контейнере. Стирание - это совершенно противоположная ситуация, когда речь идет о контейнере.
Kerrek SB
3
@skyhisi: Действительно. Это одно из законных применений постинкремента: сначала инкремент, itчтобы получить следующий, действительный итератор, а затем стереть старый. Это не работает наоборот!
Керрек С.Б.
5
Я где-то читал, что в C ++ 11 it = v.erase(it);теперь работает и для карт. То есть, erase () для всех ассоциативных элементов теперь возвращает следующий итератор. Таким образом, старый kludge, который требовал постинкремент ++ в delete (), больше не нужен. Это (если true) хорошая вещь, поскольку kludge полагался на магию переопределенного пост-инкремента в вызове функции, «исправленную» сопровождающими новичка, чтобы исключить приращение из вызова функции или обменять его до предчувствия "потому что это просто вещь стиля" и т. д.
Дьюи Морган
3
почему вы звоните it++в ifиelse блоки? Разве не достаточно будет позвонить один раз после этого?
Нурб
25
Я лично предпочитаю эту схему, которая немного яснее и проще за счет дополнительной переменной:
for(auto it = m.cbegin(), next_it = it; it != m.cend(); it = next_it){++next_it;if(must_delete){
m.erase(it);}}
Преимущества такого подхода:
инкрементатор цикла for имеет смысл как инкрементор;
операция стирания - это простое стирание, а не смешивание с логикой приращения;
после первой строки тела цикла значение itи next_itостается неизменным на протяжении всей итерации, что позволяет вам легко добавлять дополнительные операторы, ссылающиеся на них, без разбора на вопрос, будут ли они работать как задумано (за исключением того, что вы не можете использовать его itпосле стирания) ,
На самом деле я могу подумать о другом преимуществе: если цикл вызывает код, который стирает итеративную запись или предыдущие записи (а цикл ничего не знает об этом), он будет работать без какого-либо вреда. Единственное ограничение - стирает ли что-то, на что указывают next_it или наследники. Полностью очищенный список / карта также может быть проверена.
Ларсвад
Этот ответ прост и ясен, даже если цикл является более сложным и имеет несколько уровней логики, чтобы решить, следует ли удалять или выполнять другие различные задачи. Я предложил редактирование, чтобы сделать его немного проще. «next_it» может быть установлен на «it» в for для init, чтобы избежать опечаток, и поскольку операторы init и iteration оба устанавливают для него и next_it одинаковые значения, вам не нужно говорить «next_it = it;» в начале цикла.
cdgraham
1
Имейте в виду любого, кто использует этот ответ: у вас должен быть "++ next_it" внутри цикла for, а не в выражении итерации. Если вы попытаетесь переместить его в выражение итерации как «it = next_it ++», то на последней итерации, когда «it» будет установлено равным «m.cend ()», вы попытаетесь выполнить итерацию «next_it» мимо "m.cend ()", что является ошибочным.
cdgraham
6
Короче говоря "Как удалить с карты при ее повторении?"
Со старой картой подразумевается: Вы не можете
С новой картой подразумевается: почти как @KerrekSB предложил. Но есть некоторые синтаксические проблемы в том, что он написал.
Из карты 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>usingnamespace std;typedefmap<int,int> t_myMap;typedefvector<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;});return0;}
печатает:
main() ENTRY
Init1-12-13-14-15-16-1
after old style erase of 3&4..1-12-15-16-1
after new style erase of 51-12-16-1Process returned 0(0x0) execution time :0.021 s
Press any key to continue.
@ lvella см. оп. «Если я использую map.erase, это сделает недействительными итераторы».
Кашьяп
Ваш новый метод не будет работать, если после стирания карта станет пустой. В этом случае итератор будет признан недействительным. Итак, вскоре после стирания его лучше вставить if(mi.empty()) break;.
Рахат Заман
4
Черновик 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);});
Довольно грустно, а? Обычно я делаю это для создания контейнера итераторов вместо удаления во время обхода. Затем переберите контейнер и используйте 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)}
Ответы:
Стандартная ассоциативно-контейнерная идиома стирания:
Обратите внимание, что нам действительно нужен обычный
for
цикл, так как мы модифицируем сам контейнер. Цикл на основе диапазона должен быть строго зарезервирован для ситуаций, когда мы заботимся только об элементах. Синтаксис для RBFL проясняет это, даже не раскрывая контейнер внутри тела цикла.Редактировать. До C ++ 11 вы не могли стереть const-итераторы. Там вы должны сказать:
Стирание элемента из контейнера не противоречит его постоянству. По аналогии, это всегда было совершенно законно
delete p
гдеp
указатель на константу. Константность не ограничивает продолжительность жизни; константные значения в C ++ могут прекратить существование.источник
for (int i = 0; i < v.size(); i++)
. Здесь мы должны сказатьv[i]
внутри цикла, то есть мы должны явно упомянуть контейнер. RBFL, с другой стороны, представляет переменную цикла, которая непосредственно используется в качестве значения, и поэтому знание цикла внутри цикла не требуется. Это ключ к предполагаемому использованию RBFL для циклов, которые не должны знать о контейнере. Стирание - это совершенно противоположная ситуация, когда речь идет о контейнере.it
чтобы получить следующий, действительный итератор, а затем стереть старый. Это не работает наоборот!it = v.erase(it);
теперь работает и для карт. То есть, erase () для всех ассоциативных элементов теперь возвращает следующий итератор. Таким образом, старый kludge, который требовал постинкремент ++ в delete (), больше не нужен. Это (если true) хорошая вещь, поскольку kludge полагался на магию переопределенного пост-инкремента в вызове функции, «исправленную» сопровождающими новичка, чтобы исключить приращение из вызова функции или обменять его до предчувствия "потому что это просто вещь стиля" и т. д.it++
вif
иelse
блоки? Разве не достаточно будет позвонить один раз после этого?Я лично предпочитаю эту схему, которая немного яснее и проще за счет дополнительной переменной:
Преимущества такого подхода:
it
иnext_it
остается неизменным на протяжении всей итерации, что позволяет вам легко добавлять дополнительные операторы, ссылающиеся на них, без разбора на вопрос, будут ли они работать как задумано (за исключением того, что вы не можете использовать егоit
после стирания) ,источник
Короче говоря "Как удалить с карты при ее повторении?"
Из карты GCC impl (примечание GXX_EXPERIMENTAL_CXX0X ):
Пример со старым и новым стилем:
печатает:
источник
mi.erase(it++);
?if(mi.empty()) break;
.Черновик C ++ 20 содержит вспомогательную функцию
std::erase_if
.Таким образом, вы можете использовать эту функцию, чтобы сделать это как одну строку.
источник
Довольно грустно, а? Обычно я делаю это для создания контейнера итераторов вместо удаления во время обхода. Затем переберите контейнер и используйте map.erase ()
источник
Предполагая C ++ 11, вот тело цикла с одной строкой, если это согласуется с вашим стилем программирования:
Пара других незначительных изменений стиля:
Map::const_iterator
), когда это возможно / удобно, через использованиеauto
.using
для типов шаблонов, чтобыMap::const_iterator
облегчить чтение / обслуживание вспомогательных типов ( ).источник