码迷,mamicode.com
首页 > 编程语言 > 详细

c++之你真的了解vector的erase吗(网上大多都是错的)

时间:2020-06-01 13:36:23      阅读:84      评论:0      收藏:0      [点我收藏+]

标签:ack   代码   类型   环境   赋值操作符   tor   相同   r++   trait   

以下针对vector容器,编译环境为linux qt 4.7

篇幅较长,耐心看完,有错误欢迎指出

erase的定义

删除容器内元素

erase的使用

先来看一下常用的写法
第一种

   #include <iostream>
#include <vector>
using namespace std;

int main() {

    vector<int> test{1,2,3};

    for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end(); test_iterator++)
    {
        if(*test_iterator == 2) {
            test.erase(test_iterator);
        }
    }

    for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end();
        test_iterator++) {
        std::cout << *test_iterator << std::endl;
    }
    return 0;
}
    输出:
    1
    3

网上对这种就是test.erase(test_iterator)之后test_iterator指向一个被删除的地址,野指针不安全的,我现在告诉你不是的,代码运行正常删除了2,为什么正确往下看
第二种写法

#include <iostream>
#include <vector>
using namespace std;

int main() {

    vector<int> test{1,2,3};

    for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end();)
    {
        if(*test_iterator == 2) {
            test.erase(test_iterator++);
        }
        else {
            test_iterator++;         
        }
    }

    for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end();
        test_iterator++) {
        std::cout << *test_iterator << std::endl;
    }
    return 0;
}
结果:
  1
  3

也是对的,这种写法是网上所谓的正确写法之一,我告诉你这种是错的,把数据元素换一下,换成1, 2 , 2 , 3
结果是
1
2
3
和代码逻辑不符合,没有达到预期,为啥呢,往下看
第三种写法

#include <iostream>
#include <vector>
using namespace std;

int main() {

    vector<int> test{1, 2 , 2 , 3};

    for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end();)
    {
        if(*test_iterator == 2) {
            test_iterator = test.erase(test_iterator);
        }
        else {
            test_iterator++;
        }
    }

    for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end();
        test_iterator++) {
        std::cout << *test_iterator << std::endl;
    }
    return 0;
}

这是正确的写法,根据erase的返回值描述,指向下一个元素(或end())的迭代器,当数据元素重复也能达到代码逻辑正确,如果你仅仅为了删除一个元素,那么看到这里就可以了,使用第三种写法,保证你程序的正确

重点来了,如果你想了解为什么前两种写法会发生错误,下面给你讲解

erase究竟做了什么?
看源代码

    iterator
#if __cplusplus >= 201103L
    erase(const_iterator __position)
    { return _M_erase(begin() + (__position - cbegin())); }
#else
    erase(iterator __position)
    { return _M_erase(__position); }
#endif

   template<typename _Tp, typename _Alloc>
  typename vector<_Tp, _Alloc>::iterator
  vector<_Tp, _Alloc>::
  _M_erase(iterator __position)
  {
    if (__position + 1 != end())
  _GLIBCXX_MOVE3(__position + 1, end(), __position);
    --this->_M_impl._M_finish;
    _Alloc_traits::destroy(this->_M_impl, this->_M_impl._M_finish);
    return __position;
  }

以上就是一个erase的完整实现,首先传入一个迭代器,然后把传入迭代器和cbegin()进行减法,然后加上begin(),得到的还是传入的那个迭代器

然后进入_M_erase,先判断以下,

1.如果位置不是最后一个元素,进行_GLIBCXX_MOVE3操作,_GLIBCXX_MOVE3其实就是std::copy,这里不讲这个函数
只说作用,进行数据拷贝,把__position + 1, end()区间的元素拷贝到从__position开始的迭代器
2.--this->_M_impl._M_finish;,这个就是end()向前移动
尾指针进行向前移动,
3._Alloc_traits::destroy(this->_M_impl, this->_M_impl._M_finish);
进行元素的析构,注意这里进行的是对尾部元素的析构
4.返回传入的迭代器,对传入的迭代器没有进行修改!!!
来一张图片看一下整个过程

技术图片

以上就是一个erase的核心过程

下面我们开始代码验证

#include <iostream>
#include <vector>
using namespace std;

class Test {
public:
    Test() {
        std::cout << "Test()" << std::endl;
    }
    Test(int i) {
        a = i;
        std::cout << "Test(int i)" << std::endl;
    }
    Test(const Test& e) {
        a = e.a;
        std::cout << "Test(const Test& e)" << std::endl;
    }
    Test& operator=(const Test& e) {
        std::cout << "Test& operator=(const Test& e) now = " << a << std::endl;
        std::cout << "Test& operator=(const Test& e) e = " << e.getA() << std::endl;
        a = e.a;
        return *this;
    }

    int getA() const{
        return a;
    }

    ~Test() {
        std::cout << "~Test() = " << a << std::endl;
    }
private:
    int a;
};


int main() {

    vector<Test> test;

    Test test1(1);
    Test test2(2);
    Test test3(3);

    test.push_back(test1);
    test.push_back(test2);
    test.push_back(test3);

    for(vector<Test>::iterator test_iterator = test.begin(); test_iterator != test.end();)
    {
        if(test_iterator->getA() == 2) {
            std::cout << "erase start" << std::endl;
            test_iterator = test.erase(test_iterator);
            std::cout << "erase end" << std::endl;
        }
        else {
            test_iterator++;
        }
    }

    for(vector<Test>::iterator test_iterator = test.begin(); test_iterator != test.end();
        test_iterator++) {
        std::cout << test_iterator->getA() << std::endl;
    }
    return 0;
}


输出结果只看erase start开始到erase end的
erase start
Test& operator=(const Test& e) e = 3
~Test() = 3
erase end
1
3

定义一个简单的Test类,使用成员变量a确定析构的是哪个类,进行test2(2)的erase,结果和分析一致,对元素值为2的元素调用赋值操作符,参数元素值为3的类进行拷贝,删除后元素剩下的为 1 3!!
##关于erase的返回值
  经过上面的源码分析,相比你也知道了,传入的参数和返回值是同一个,不信吗,代码验证,换一个简单的

include

include

using namespace std;

int main() {

vector<int> test{1,2,3,4};



for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end();)
{
    if(*test_iterator == 3) {
        std::cout << &test_iterator << std::endl;  //test_iterator的地址
        std::cout << &*test_iterator << std::endl; //test_iterator指向空间的地址
        test_iterator = test.erase(test_iterator);
        std::cout << &test_iterator << std::endl;  //test_iterator的地址
        std::cout << &*test_iterator << std::endl; //test_iterator指向空间的地址
        std::cout << "erase end" << std::endl;
    }
    else {
        test_iterator++;
    }
}

for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end();
    test_iterator++) {
    std::cout << *test_iterator << std::endl;
}
return 0;

}
结果
0x7ffef8e11d50
0x18b5c28
////////////////
0x7ffef8e11d50
0x18b5c28
erase end
1
2
4

  test_iterator的地址本身就是不变的,它只不过是一个类似智能指针管理传入的元素,通过iterator调用
  &*test_iterator代表指向的空间也就是3的地址,可以看到删除前后地址不变
  改一下代码
  test.erase(test_iterator);
  不进行赋值了打印地址,得到的仍然相同
  标准库描述的意思是返回值是指向删除元素下一个元素的迭代器,这没问题,因为删除元素的地址的内容换成了下一个元素,然后把这个迭代器返回就是原来传入的参数迭代器
##所以最开始的那几个写法你应该了解了吧
  test.erase(test_iterator++);
  这种写法就是个笑话,会把指针test_iterator指向下一个元素的下一个元素
  test.erase(test_iterator);
  test_iterator = test.erase(test_iterator);等效的
  你可以
      for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end();)
    {
        if(*test_iterator == 3) {
            test_iterator = test.erase(test_iterator);
        }
        else {
            test_iterator++;
        }
    }
    也可以
    for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end();)
    {
        if(*test_iterator == 3) {
            test.erase(test_iterator);
        }
        else {
            test_iterator++;
        }
    }
##总结
  1.iterator只不过是一个内置类型,类似智能指针对元素访问进行封装
  2.erase的操作是由后向前赋值的过程
  3.每次erase之后析构的是最后的无用元素
  4.erase的传入参数和返回值相同

c++之你真的了解vector的erase吗(网上大多都是错的)

标签:ack   代码   类型   环境   赋值操作符   tor   相同   r++   trait   

原文地址:https://www.cnblogs.com/zero-waring/p/13024357.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!