擦除和删除之间的区别

Nav*_*een 47 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";
}
Run Code Online (Sandbox Code Playgroud)

两种情况下的输出均为2,2.

但是,如果我使用erase删除这样的东西:

a.erase(std::remove(a.begin(), a.end(), 1), a.end());
Run Code Online (Sandbox Code Playgroud)

我得到输出为2.

所以我的问题是:

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

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

我在Scott Meyer的Effective STL书中读到了关于擦除删除习语的内容.但我仍然有这种困惑.

j_r*_*ker 52

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

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

int foo[100];

...

remove(foo, foo + 100, 42);    // Remove all elements equal to 42
Run Code Online (Sandbox Code Playgroud)

这里很明显remove()无法调整数组的大小!


dir*_*tly 17

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

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

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

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

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

  • 嗯.实际上std :: remove()不会将已删除的元素移动到容器的末尾 - 容器的其余位置将包含其原始值.(它必须以这种方式工作以使用前向迭代器保留O(n)时间.) (5认同)
  • 最后,我认为这只是糟糕的措辞,我不应该删除我的答案.我认为在returned_iterator之后的元素只有一些不确定的值.感叹:(呵呵 (2认同)

Shi*_*hah 15

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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,删除只是向上移动序列中的元素,覆盖您要删除的值.所以你要删除的值确实消失了,但那么问题是什么?假设你有值为{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());
}
Run Code Online (Sandbox Code Playgroud)

因此擦除操作实际上会改变容器的大小,从而释放内存.

删除 - 删除成语

的组合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());
Run Code Online (Sandbox Code Playgroud)

这被称为删除擦除习语.为什么这样设计?洞察力是查找元素的操作更通用且独立于底层容器(仅依赖于迭代器).但是擦除操作取决于容器如何存储内存(例如,您可能有链表而不是动态数组).因此STL希望容器在提供通用的"删除"操作时自行擦除,因此所有容器都不必实现该代码.在我看来,该名称非常具有误导性,std::remove应该被称为std::find_move.

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


Jay*_*llo 11

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

在向量中,该函数erase只能按位置删除元素,例如:

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

迭代器擦除(迭代器第一个,迭代器最后一个);

但如果你想删除符合某种条件的元素,你可以将它与std::removeor结合起来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 
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

结论:

如果你想删除符合某些条件的元素,你可以使用vector::iterator erase (iterator first, iterator last); 本质。

首先获取范围开始:

自动最后 = std::remove(vec.begin(), vec.end(), equal_condition_value);

按范围擦除(始终使用 end())

vec.erase(最后一个, vec.end());

引用:

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


小智 6

最简单的我可以想出:

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

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


Mol*_*mer 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"
    }

}
Run Code Online (Sandbox Code Playgroud)

如你所见,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"
Run Code Online (Sandbox Code Playgroud)

如果你将从步骤5获得的迭代器传递给"erase()",它将知道从那里擦除字符串的结尾重新调整字符串的大小