vector::尾插_干货 | 名企高频考点之谈谈你所理解的vector数据插入(一)

c4631c51850f89624200686c5500cdf0.png

点击蓝字关注我哦

aa3bf7aca9ac4cf3ec3c51ad015f9952.png 以下是本期干货视频 视频后还附有文字版本哦 a0d60d5f14ce817b4b0ab9014fd17332.png 5ce0ac61df04b737f9cbf85679692a53.png ▼ 《名企高频考点之-谈谈你所理解的vector数据插入(一)》 ▼ ps:请在WiFi环境下打开,如果有钱任性请随意1. vector插入面试官更想听到 在面试中,vector的插入经常会被考到,可能会被面试官问到,也有可能在OJ笔试中遇到,操作不当可能就会导致程序效率低下而不能完全通过测试用例,或者不能给出程序简洁的书写方式。 那面试时被问到:vector是如何插入的?诸如此类问题,大家一定要去琢磨面试官更想听到什么答案,说白了就是想听到:
  • 同学是否掌握vector的不同插入方式

  • 不同插入方式背后又会发生什么事情,效率怎么样,如何选择合适的插入方式

  • 插入后可能会引起什么后果

  • 有没有分类作答,回答条理是否清晰

以上就是面试官想要听到的,如果能够揣摩到面试官的心思去作答,必能俘获其芳心。 vector的插入从插入位置上可以分为两类:
  1. 老实巴交的push_back尾插
  2. 专横霸气的insert任意位置的插入
本节我们现在讨论push_back尾插。

2. C++98中的push_back尾插
2.1 内置类型元素插入
内置类型的元素尾插非常简单,定义好了vector之后,便可以直接插入,而且该容器在插入时可以动态增长,用户只管插入数据,一般不用担心数据放不下的问题,降低了用户的使用成本。
// 验证:1. push_back是尾插   2. 在空间允许范围内,可以插入任意多个元素void TestPushBack1(){    vector v;    for(int i = 0; i < 100/*1000*/; ++i)        v.push_back(i);    for(auto e : v)        cout<< e << endl;    cout<}// 验证:在插入元素期间,vector会进行扩容void TestPushBack2(){    vector v;    size_t sz = v.capacity();    for(int i = 0; i < 100; ++i)    {        v.push_back(i);        if(sz != v.capacity())        {            sz = v.capacity();            cout<< sz << endl;        }    }}
2.2 自定义类型元素插入
自定义类型元素在插入时,实际插入的是对象的一份拷贝。
class Date{public:    Date(int year = 1970, int month = 1, int day = 1)        : _year(year)        , _month(month)        , _day(day){        std::cout << "Date(int,int,int): " << this << std::endl;}    Date(const Date& d)        : _year(d._year)        , _month(d._month)        , _day(d._day){        std::cout << "Date(const Date&): " << this << std::endl;}private:    int _year;    int _month;    int _day;};// 验证:自定义类型元素push_back的是该元素的拷贝void TestPushBack3(){    Date d(2020, 4, 17);    vector v;    v.push_back(d);}输出结果:Date(int,int,int): 00AFFBB4Date(const Date&): 00CEBD88
从打印结果可以看出,对于Date类型对象d在push_back()时,插入的确实是对象的拷贝。 如果想要提高程序运行效率,不考虑兼容性的情况下,可以使用C++11提供的emplace_back进行就地构造。
void TestPushBack4(){    vector v;    v.emplace_back(2020, 4, 17);    cout << &v[0] << endl;}程序输出结果:Date(int,int,int): 00865AE8    调用构造函数直接在vector中构造对象00865AE8                       可以看到vector中0好位置对象与构造函数打印对象地址相同
那如果用户没有显式定义拷贝构造函数时,还能进行插入吗?
class Date{public:    Date(int year = 1970, int month = 1, int day = 1)        : _year(year)        , _month(month)        , _day(day){        std::cout << "Date(int,int,int): " << this << std::endl;}private:    int _year;    int _month;    int _day;};// 验证:如果没有定义拷贝构造函数,能否正常进行尾插void TestPushBack5(){    Date d(2020, 4, 17);    vector v;    for(int i = 0; i < 100; ++i)        v.push_back(d);    std::cout << v.size() << std::endl;}程序输出:100
结论:对于Date类而言,即使用户没有显式定义拷贝构造函数,仍旧可以正常进行插入。 问题:那能否说明对于没有显式拷贝构造函数的任意类型,都可以进行尾插呢?
class String{public:    String(const char* str = ""){        if (nullptr == str)            str = "";        _str = new char[strlen(str) + 1];        strcpy(_str, str);        std::cout << "String(const char*):" << this << std::endl;}    ~String(){        if (nullptr != _str)        {            delete[] _str;            _str = nullptr;        }        std::cout << "~String():" << this << std::endl;}private:    char* _str;};void TestPushBack6(){    String s("hello");    std::vector v;    v.push_back(s);}
上述程序在运行期间,并不能够正常运行,发生崩溃。

0be6950419ededed0b259f8782b45bbd.png

同样是没有拷贝构造函数,为什么Date可以直接插入,而String类则不行呢?原因分析如下: 1.如果用户没有显式定义拷贝构造函数时,编译器会按照浅拷贝方式生成一个默认拷贝构造函数 2.浅拷贝:将一个对象中内容原封不动的拷贝到新对象中 如果类中未涉及到资源管理时,不会出现任何问题,比如Date类 如果类中涉及到资源管理时,浅拷贝会让多个对象共享同一份资源,在对象销毁时同一份资源释放多次而导致程序崩溃。 1c9877239c619ec9dc678e52a47f5819.png 在销毁时,s2对象调用其析构函数会先将0x12345678空间释放掉,但s1并不知道,其在销毁时会再次使用0x12345678空间,而造成程序崩溃。 解决方式:给String类添加深拷贝的拷贝构造函数完成拷贝
// 深拷贝就是让每个对象都拥有自己独立的资源,在释放时就不会产生影响String(const String& s)    : _str(new char[strlen(s._str)+1]){     strcpy(_str, s._str);  }
2d7f51b5446a027fd483f62ac11c25d3.png 深拷贝也是面试中常考问题,如果同学们对深拷贝感兴趣,可以在后台留言。 3. C++11新增右值引用push_back尾插
3.1 传统插入的问题分析
class String{public:    String(const char* str = ""){        if (nullptr == str)            str = "";        _str = new char[strlen(str) + 1];        strcpy(_str, str);        std::cout << "String(const char*):" << this << std::endl;}    // 深拷贝构造函数    String(const String& s)        : _str(new char[strlen(s._str)+1])    {        strcpy(_str, s._Str);            }    ~String(){        if (nullptr != _str)        {            delete[] _str;            _str = nullptr;        }        std::cout << "~String():" << this << std::endl;}private:    char* _str;};int main(){    vector v;    v.push_back(String("hello"));    return 0;}
通过前文分析指导,vector在push_back时尾插的是对象的拷贝,但是对于v.push_back(String("hello"))方式,是构建一个匿名对象向vector中尾插,尾插结束后该对象被销毁,初听之下没有问题,细细品味有不足之处:

882d77cf71a9aed2e1447a79e89afda6.png

3.2 C++11提供移动语义的push_back方式来提高效率
如果想要支持移动语义,String类中必须提供移动构造:
// 移动构造:将参数s中资源转移给当前对象,然后让s中资源指向空,因为s马上要被销毁// 注意:参数必须是右值引用类型的,此处不能添加const,否则不能转移资源String(String&& s)    : _str(s._str){    s._str = nullptr;        }
再次运行程序时可以看到不会再调用拷贝构造函数,而是调用移动构造,少了拷贝构造中新空间的申请,提高程序运行效率。

d438e4da6d1392c248bf4cab010011d6.png

4. 总结 本节主要对push_back进行了详细讨论,以及插入背后所涉及到的问题及其优化,希望对同学们的日后使用以及面试能有所帮助,谢谢。

994012748b37c4dc6c795a231233fc6b.png

作者:时亮益 审核:王海斌 编辑:小丸子

af7c5580ab91e67ce6bb7929fa582910.png

好看,就要点个"在看"

fdc1db5bc221ff71ca231a69e6267cca.gif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值