我对使用std :: remove算法之间的区别感到困惑。具体来说,我无法理解使用此算法时要删除的内容。我写了一个像这样的小测试代码:
std::vector<int> a;
a.push_back(1);
a.push_back(2);
std::remove(a.begin(), a.end(), 1);
int s = a.size();
std::vector<int>::iterator iter = a.begin();
std::vector<int>::iterator endIter = a.end();
std::cout<<"Using iter...\n";
for(; iter != endIter; ++iter)
{
std::cout<<*iter<<"\n";
}
std::cout<<"Using size...\n";
for(int i = 0; i < a.size(); ++i)
{
std::cout<<a[i]<<"\n";
}
两种情况下的输出均为2.2。
但是,如果我使用erase删除这样的东西:
a.erase(std::remove(a.begin(), a.end(), 1), a.end());
我得到输出为2。
所以我的问题是:
(1)。有没有使用std :: remove而不是使用擦除功能。
(2)。即使在执行std :: remove之后,为什么a.size()返回2而不是1?
我在Scott Meyer的Effective STL书中读到了关于擦除删除习语的内容。但我仍然有这种困惑。
答案 0 :(得分:52)
remove()
实际上并没有从容器中删除元素 - 它只在已删除的元素之上分流未删除的元素。关键是要意识到remove()
不仅适用于容器,而且适用于任意前向迭代器对:这意味着它不能实际删除元素,因为任意迭代器对不一定能够删除元素。
例如,指向常规C数组的开头和结尾的指针是前向迭代器,因此可以与remove()
一起使用:
int foo[100];
...
remove(foo, foo + 100, 42); // Remove all elements equal to 42
显然remove()
无法调整数组的大小!
答案 1 :(得分:17)
std::remove
不会删除实际对象,而是将它们推送到容器的末尾。通过擦除实现内存的实际删除和释放。所以:
(1)。有没有使用std :: remove而不是使用擦除功能。
是的,它有助于将一对迭代器添加到新序列中,而无需担心正确的解除分配等。
(2)。即使在执行std :: remove之后,为什么a.size()返回2而不是1?
容器仍然保留这些对象,您只有一组新的迭代器可供使用。因此,尺寸仍然是过去的。
答案 2 :(得分:12)
std :: remove做什么?
这里是std::remove
的伪代码。花几秒钟看看它做了什么,然后阅读解释。
Iter remove(Iter start, Iter end, T val) {
Iter destination = start;
//loop through entire list
while(start != end) {
//skip element(s) to be removed
if (*start == val) {
start++;
}
else //retain rest of the elements
*destination++ = *start++;
}
//return the new end of the list
return destination;
}
请注意,删除只是向上移动序列中的元素,覆盖您要删除的值。所以你要删除的值确实消失了,但那么问题是什么?假设你有值为{1,2,3,4,5}的向量。在为val = 3调用remove后,向量现在具有{1,2,4,5,5}。也就是说,4和5向上移动,使得3从向量中消失,但向量的大小没有改变。此外,向量的末尾现在包含额外的5的剩余副本。
vector :: erase做什么?
std::erase
占据您想要摆脱的范围的开始和结束。它不需要要删除的值,只需要开始和结束范围。这是关于它如何工作的伪代码:
erase(Iter first, Iter last)
{
//copy remaining elements from last
while (last != end())
*first++ = *last++;
//truncate vector
resize(first - begin());
}
因此擦除操作实际上会改变容器的大小,从而释放内存。
删除 - 擦除习惯用法
std::remove
和std::erase
的组合允许您从容器中删除匹配的元素,以便在删除元素时实际上会截断容器。以下是如何做到这一点:
//first do the remove
auto removed = std::remove(vec.begin(), vec.end(), val);
//now truncate the vector
vec.erase(removed, vec.end());
这被称为删除擦除习语。为什么这样设计?洞察力是查找元素的操作更通用且独立于底层容器(仅依赖于迭代器)。但是擦除操作取决于容器如何存储内存(例如,您可能有链表而不是动态数组)。所以STL希望容器能够自己擦除,同时提供通用&#34;删除&#34;操作,所以所有容器都不必实现该代码。在我看来,该名称非常具有误导性,std::remove
应该被称为std::find_move
。
注意:上面的代码是严格的伪代码。实际的STL实现更加智能,例如,使用std::move
而不是复制。
答案 3 :(得分:6)
最简单的我可以提出:
erase()
是您可以对容器中的元素执行的操作。给定容器中的迭代器/索引,erase( it )
将从容器中删除迭代器引用的东西。
remove()
是你可以对范围做的事情,它会重新安排范围,但不会
擦除范围内的任何内容。
答案 4 :(得分:6)
我遇到同样的问题,试图了解其中的差异。 到目前为止给出的解释是正确的,但我只是在看到一个例子之后理解它们;
#include <algorithm>
#include <string>
#include <iostream>
#include <cctype>
int main()
{
std::string str1 = "Text with some spaces";
std::string::iterator it = remove(str1.begin(), str1.end(), 't');
std::cout << str1 << std::endl;// prints "Tex wih some spaceses"
for (str1.begin();it != str1.end(); ++it)
{
std::cout << *it; //prints "es"
}
}
如您所见,remove,只将小写't'移动到字符串的末尾,同时将新的迭代器返回到新字符串的末尾(new string是旧字符串,直到删除的位置)插入元素) 这就是为什么当你打印从“删除”
获得的迭代器时 "Text with some spaces"
^ ^removes both 't', then shift all elements forward -1 //what we want to remove
"Text with some spaces"
^ end of string -2 //original state of string
"Tex with some spacess"
^end of string -3 //first 't' removed
"Tex wih some spaceses"
^end of string -4 //second 't' removed
"Tex wih some spaceses"
^new iterator that remove() returned -5 // the state of string after "remove" and without "erase"
如果你将从步骤5获得的迭代器传递给“erase()”,它将知道从那里擦除字符串的结尾重新调整进程中的字符串
答案 5 :(得分:3)
删除不“真正”删除 什么,因为它不能。
为了“实际”从容器中删除元素,您需要访问容器API。其中remove仅适用于迭代器,而不管迭代器指向哪些容器。因此,即使删除想要“实际删除”,它也不能。
通过以下未删除的元素删除覆盖“已删除”元素,然后由调用者决定使用返回的新逻辑end
而不是原始end
。
在您的情况下,从1
移除逻辑删除的vector
,但大小仍为2。擦除实际上从矢量中删除了元素。 [从向量new end
到old end
]
remove
的主要思想是它不能改变元素的数量,它只是根据标准从一个范围中删除元素。
答案 6 :(得分:0)
要删除容器(如向量)中具有某些条件(等于某个值或其他条件,例如小于)的元素,它总是将函数成员函数erase
和std::remove
或std::remove_if
组合在一起。 / p>
在向量中,函数erase
可以按位置删除元素,例如:
迭代器擦除(迭代器位置);
迭代器擦除(迭代器在先,迭代器在后);
但是,如果要在某种条件下擦除元素,可以将其与std::remove
或std::remove_if
组合。
例如,您要删除以下向量中的所有元素6
:
std::vector<int> vec{6, 8, 10, 3, 4, 5, 6, 6, 6, 7, 8};
// std::remove move elements and return iterator for vector erase funtion
auto last = std::remove(vec.begin(), vec.end(), 6);
for(int a:vec)
cout<<a<<" ";
cout<<endl;
// 8 10 3 4 5 7 8 6 6 7 8
vec.erase(last, vec.end());
for(int a:vec)
cout<<a<<" ";
cout<<endl;
// 8 10 3 4 5 7 8
std::remove
的工作方式如下,它不会擦除任何元素,它只是移动元素并返回迭代器。
可能的实现:
template< class ForwardIt, class T >
ForwardIt remove(ForwardIt first, ForwardIt last, const T& value)
{
first = std::find(first, last, value);
if (first != last)
for(ForwardIt i = first; ++i != last; )
if (!(*i == value))
*first++ = std::move(*i);
return first;
}
结论:
如果要删除某些情况下的元素,则可以使用vector::iterator erase (iterator first, iterator last);
。
首先开始范围:
auto last = std :: remove(vec.begin(),vec.end(),equal_condition_value);
按范围擦除(始终与end()一起使用)
vec.erase(last,vec.end());
引用: