擦除和删除之间的区别

时间:2009-04-28 18:41:57

标签: c++ stl

我对使用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书中读到了关于擦除删除习语的内容。但我仍然有这种困惑。

7 个答案:

答案 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::removestd::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 endold end]

remove的主要思想是它不能改变元素的数量,它只是根据标准从一个范围中删除元素。

答案 6 :(得分:0)

要删除容器(如向量)中具有某些条件(等于某个值或其他条件,例如小于)的元素,它总是将函数成员函数erasestd::removestd::remove_if组合在一起。 / p>

在向量中,函数erase可以按位置删除元素,例如:

迭代器擦除(迭代器位置);

迭代器擦除(迭代器在先,迭代器在后);

但是,如果要在某种条件下擦除元素,可以将其与std::removestd::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的工作方式如下,它不会擦除任何元素,它只是移动元素并返回迭代器。

enter image description here

可能的实现:

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

引用:

https://en.cppreference.com/w/cpp/algorithm/remove