zoukankan      html  css  js  c++  java
  • 【STL深入理解】vector

    这篇文章不打算讲述vector的基本用法,而是总结一下近期我大量阅读C++经典书籍时遇到的一些关于vector的容易忽略的知识点,特意将它们记录下来,以便以后查阅。

    1.v[0]和v.at(0)的区别

    void f(vector<int>& v)
    {
        v[0];  //A
        v.at(0);  //B
    }
    

    观察该函数,我们使用A和B的形式来访问v的元素,他们有什么区别?他们唯一区别就是如果v空则B会抛出std::out_of_range的异常,至于A行为标准未加任何说明。所以B方式可以防止越界操作,但是B方式较A方式效率要低(因为加入了越界检查)。

    2.resize()和reserve()是不同的操作

    int main()
    {
        vector<int> v;
        v.reserve(2);
        cout << "capacity: " << v.capacity() << endl;
        assert(v.capacity() == 2);
        v[0] = 1;  //A
        v[1] = 2;  //B
    
        return 0;
    }
    

    上面的代码是有问题的,我在VS2015下进行编译运行时弹出“vector越界访问”的错误。那该段程序错在哪里?

    1. 首先这里的断言可能会失败。因为reserve操作将保证vector容量>=2。而且这里的断言也是多余的。
    2. 然后A和B的复制是有问题的,因为该程序忽视了resize/size和reserve/capacity的区别。size()告诉你容器中实际有多少个元素,resize()则会在容器末尾添加或者删除元素,使得容器达到指定大小;capacity()告诉你最少添加多少个元素才会导致容器的重新分配内存,而reserve()在必要时候总是容器内部缓冲区扩至一个更大的内容,reserve()并不改变容器中元素的数量,它仅影响vector预先分配多大的内存空间。

    所以上面的代码修改可以这么做:

    int main()
    {
        vector<int> v;
        v.resize(2);
    
        v[0] = 1;
        v[1] = 2;
    
        return 0;
    }
    

    或者使用pushback()来添加元素。

    3.vector增长的方式

    上面第二点说了vector中size()和capacity()是不同的意思,造成这一现象的原因就是vector的的增长方式有些不同。用一句话来总结vector的的增长方式就是“重新配置,移动数据,释放原空间”。

    标准库实现者为了尽量减少容器空间重新分配次数,他们采取这样一种策略:当不得不获取新的内存空间时,vector通常会分配比新的空间需求更大的内存空间。容器预留这些空间作为备用,可以用来保存更多的元素。这样,就不需要每次添加新元素都重新分配容器空间了。上面说到“容器预留这些空间作为备用”,这里的预留空间时多少呢?根据《STL源码剖析》的说法,当增加新元素时,如果超过当时的容量,则容量会扩充至原理容量的2倍;如果两倍仍不足,就扩张到足够大的容量。

    vector中有三个重要迭代器:

    以下图像形象表示出vector的增长方式以及size与capacity的区别。

    为了验证vector内存容量的增长策略,我特意做了以下实验。

    以下代码在centos 7下编译运行,其显示结果与《STL源码剖析》说法一致。

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    //在g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-11)下编译运行
    int main()
    {
        vector<int> v(2,9);  //9 9
        cout << "size=" << v.size() << endl;  //size=2
        cout << "capacity=" << v.capacity() << endl; //capacity=2
    
        v.push_back(1);  //9 9 1
        cout << "size=" << v.size() << endl;  //size=3
        cout << "capacity=" << v.capacity() << endl; //capacity=4
        
        v.push_back(2); //9 9 1 2
        cout << "size=" << v.size() << endl;  //size=4
        cout << "capacity=" << v.capacity() << endl; //capacity=4
    
        v.push_back(3);  //9 9 1 2 3
        cout << "size=" << v.size() << endl;  //size=5
        cout << "capacity=" << v.capacity() << endl; //capacity=8
    
        v.push_back(4);  //9 9 1 2 3 4
        cout << "size=" << v.size() << endl;  //size=6
        cout << "capacity=" << v.capacity() << endl; //capacity=8
    
        v.push_back(5);  // 9 9 1 2 3 4 5
        cout << "size=" << v.size() << endl;  //size=7
        cout << "capacity=" << v.capacity() << endl; //capacity=8
    
        v.pop_back();
        v.pop_back();
        cout << "size=" << v.size() << endl;  //size=5
        cout << "capacity=" << v.capacity() << endl; //capacity=8
    
        v.pop_back();
        v.pop_back();
        cout << "size=" << v.size() << endl;  //size=3
        cout << "capacity=" << v.capacity() << endl; //capacity=8
    
        v.clear();
        cout << "size=" << v.size() << endl;  //size=0
        cout << "capacity=" << v.capacity() << endl; //capacity=8
    
        return 0;
    }
    

    但是在VS2015下编译运行,效果就不一样了。

    查阅资料知道,这里容量增长方式是:每次扩容50%

    依次看来,在VS2015下采用的标准库版本与Linux下的版本应该是不一样的。

    4.迭代器失效情况总结

    对于vector而言,添加和删除操作可能使容器的部分或者全部迭代器失效。为什么迭代器会失效呢?vector元素在内存中是顺序存储,试想:如果当前容器中已经存在了10个元素,现在又要添加一个元素到容器中,但是内存中紧跟在这10个元素后面没有一个空闲空间,而vector的元素必须顺序存储一边索引访问,所以我们不能在内存中随便找个地方存储这个元素。于是vector必须重新分配存储空间,用来存放原来的元素以及新添加的元素:存放在旧存储空间的元素被复制到新的存储空间里,接着插入新的元素,最后撤销旧的存储空间。这种情况发生,一定会导致vector容器的所有迭代器都失效。

    先看一个错误的例子:

    vector<mission>::iterator itr = vm.begin();  
    while (itr != vm.end())  
    {  
        if ((*itr).getStartTime() <= nowTime)  
        {  
            vm.erase(itr);  
        }  
        itr++;  
    }  
    

    这段代码运行起来会crash,其原因当然是迭代器失效了,我们还用了它。

    因为在erase操作后,原迭代器是相当于一个野指针的状态,对其++必定出错。
    而erase的返回值就是指向被删除的元素的下一个元素的迭代器,我们没必要再次++了。

    正确写法:

    vector<mission>::iterator itr = vm.begin();  
    while (itr != vm.end())  
    {  
        if ((*itr).getStartTime() <= nowTime)  
        {  
            itr = vm.erase(itr);  
        }  
        else  
        {  
            itr++;  
        }  
    }  
    

    vector迭代器的几种失效的情况:

    1. 当插入一个元素后,插入位置之后的元素迭代器肯定失效。
    2. 当插入一个元素后,capacity返回值与没有插入元素之前相比有改变(即存储空间重新分配),则需要重新加载整个容器,此时指向容器的迭代器都会失效。
    3. 当进行删除操作(erase,pop_back)后,指向删除点的迭代器全部失效;指向删除点后面的元素的迭代器也将全部失效
  • 相关阅读:
    PIE SDK SFIM融合
    PIE SDK PCA融合
    c# 粘贴复制
    dev gridview 单元格值拖拽替换
    sql 行数据找出最大的及所有数据最大的
    mvc 登陆界面+后台代码
    mvc控制器返回操作结果封装
    Java 未来行情到底如何,来看看各界人士是怎么说的
    Java工程师修炼之路(校招总结)
    ​为什么我会选择走 Java 这条路?
  • 原文地址:https://www.cnblogs.com/skyfsm/p/7488053.html
Copyright © 2011-2022 走看看