擦除和删除之间的区别

问题描述 投票:47回答:7

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

c++ stl
7个回答
52
投票

remove()实际上并没有从容器中删除元素 - 它只在已删除的元素之上分流未删除的元素。关键是要意识到remove()不仅适用于容器,而且适用于任意前向迭代器对:这意味着它实际上不能删除元素,因为任意迭代器对不一定能够删除元素。

例如,指向常规C数组的开头和结尾的指针是前向迭代器,因此可以与remove()一起使用:

int foo[100];

...

remove(foo, foo + 100, 42);    // Remove all elements equal to 42

显而易见,remove()无法调整数组的大小!


17
投票

std::remove不会删除实际的对象,而是将它们推到容器的末尾。通过擦除实现内存的实际删除和释放。所以:

(1)。有没有使用std :: remove而不是使用擦除功能。

是的,它有助于将一对迭代器添加到新序列中,而无需担心正确的解除分配等。

(2)。即使在执行std :: remove之后,为什么a.size()返回2而不是1?

容器仍然保存到这些对象,您只有一组新的迭代器可供使用。因此,尺寸仍然是以前的样子。


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希望容器在提供通用的“删除”操作时自行擦除,因此所有容器都不必实现该代码。在我看来,这个名字是非常误导的,std::remove应该被称为std::find_move

注意:上面的代码是严格的伪代码。实际的STL实现更加智能,例如,使用std::move而不是copy。


6
投票

最简单的我可以想出:

erase()是你可以对容器中的元素做的事情。给定一个容器的迭代器/索引,erase( it )删除迭代器从容器引用的东西。

remove()是你可以对范围做的事情,它重新安排范围,但不会删除范围内的任何东西。


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()”,它将知道从那里擦除字符串的结尾重新调整字符串的大小


3
投票

删除不“真正”删除任何东西,因为它不能。

为了“实际”从容器中删除元素,您需要访问容器API。其中remove仅适用于迭代器,而不管迭代器指向哪些容器。因此,即使删除想要“实际删除”,它也不能。

删除通过以下未删除的元素覆盖“已删除”元素,然后由调用者决定使用返回的新逻辑end而不是原始end

在你的情况下从1删除逻辑上删除的vector,但大小仍为2本身。擦除实际上从矢量中删除了元素。 [从矢量new endold end]

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


0
投票

要在像vector这样的容器中删除具有某些条件(等于某些值或其他条件,如小于)的元素,它总是组合函数成员函数erasestd::removestd::remove_if

在向量中,函数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

© www.soinside.com 2019 - 2024. All rights reserved.