详解C++ STL中vector扩容机制

 更新时间:2024年03月28日 09:51:47   作者:number=10086  
vector是表示可以改变大小的数组的序列容器,就像数组一样,vector对其元素使用连续的存储位置,这篇文章将给大家详细介绍C++ STL中vector扩容机制,文中通过代码示例介绍的非常详细,需要的朋友可以参考下
(福利推荐:【腾讯云】服务器最新限时优惠活动,云服务器1核2G仅99元/年、2核4G仅768元/3年,立即抢购>>>:9i0i.cn/qcloud

(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅2998元/3年,立即抢购>>>:9i0i.cn/aliyun

vector是STL提供的动态数组,它会在内部空间不够用时动态的调整自身的大小,调整过程中会有大量的数据拷贝,为了减少数据拷贝的次数vector会在调整空间的时候尽量多申请一些空间,这些预留出的空间可以很大程度上减少拷贝的发生。

在windows环境中使用vs运行这段代码

#include<iostream>
#include<vector>
using namespace std;
void fun(vector<int>&vec){
	vec.push_back(1);
    cout<<&vec[0]<<vec.size()<<" "<<vec.capacity()<<endl;
}
int main(){
    vector<int>vec;
    for(int i=0;i<10;i++) fun();
}

打印内容依次为,首元素地址,已经使用的空间,容器的容量

首先看push_back源码,第一个拷贝版本,第二个移动版本,需要注意的是这里第二个不是万能引用而是右值引用,下面看emplace_back代码

void push_back(const _Ty& _Val) { // insert element at end, provide strong guarantee
        emplace_back(_Val);
    }

    void push_back(_Ty&& _Val) { // insert by moving into element at end, provide strong guarantee
        emplace_back(_STD move(_Val));
    }

emplace_back使用了可变参数,并且对参数使用了万能引用,从而使得可以在插入节点时调用对应的构造函数,比如:vector<pair<int,int>>vec;vec.emplace_back(1,2);这么做可以很大程度上简化代码的书写,同时还可以减少一次移动或是拷贝的过程,decltype(auto)这个没什么说的根据返回值推导返回值类型。

回归正题,执行emplace_back的时候会判断容量是否充足,size!=capacity的时候会直接加入元素,否则的话会调用_Emplace_reallocate函数进行扩容。

template <class... _Valty>
    decltype(auto) emplace_back(_Valty&&... _Val) {
        // insert by perfectly forwarding into element at end, provide strong guarantee
        auto& _My_data   = _Mypair._Myval2;
        pointer& _Mylast = _My_data._Mylast;
        if (_Mylast != _My_data._Myend) {
            return _Emplace_back_with_unused_capacity(_STD forward<_Valty>(_Val)...);
        }

        _Ty& _Result = *_Emplace_reallocate(_Mylast, _STD forward<_Valty>(_Val)...);
#if _HAS_CXX17
        return _Result;
#else // ^^^ _HAS_CXX17 ^^^ // vvv !_HAS_CXX17 vvv
        (void) _Result;
#endif // _HAS_CXX17
    }

下面看扩容代码,代码有点长我直接在代码上加注释了

template <class... _Valty>
    pointer _Emplace_reallocate(const pointer _Whereptr, _Valty&&... _Val) {
        // reallocate and insert by perfectly forwarding _Val at _Whereptr
        _Alty& _Al        = _Getal();
        auto& _My_data    = _Mypair._Myval2;//使用的长度
        pointer& _Myfirst = _My_data._Myfirst;//容器开始的位置
        pointer& _Mylast  = _My_data._Mylast;//容器末尾

        _STL_INTERNAL_CHECK(_Mylast == _My_data._Myend); // check that we have no unused capacity

        const auto _Whereoff = static_cast<size_type>(_Whereptr - _Myfirst);//插入元素的位置,这个函数insert也有在用,所以插入的位置不一定在尾部
        const auto _Oldsize  = static_cast<size_type>(_Mylast - _Myfirst);//容器大小

        if (_Oldsize == max_size()) {//长度超过数组的最大长度时报错
            _Xlength();
        }

        const size_type _Newsize     = _Oldsize + 1;
        const size_type _Newcapacity = _Calculate_growth(_Newsize);//调用扩容函数

        const pointer _Newvec           = _Al.allocate(_Newcapacity);//申请新的空间
        const pointer _Constructed_last = _Newvec + _Whereoff + 1;//计算新的末尾
        pointer _Constructed_first      = _Constructed_last;

        _TRY_BEGIN
        _Alty_traits::construct(_Al, _Unfancy(_Newvec + _Whereoff), _STD forward<_Valty>(_Val)...);//在新的空间添加新的元素
        _Constructed_first = _Newvec + _Whereoff;
		//下面是根据插入元素的位置判断如何拷贝
        if (_Whereptr == _Mylast) { // at back, provide strong guarantee
            _Umove_if_noexcept(_Myfirst, _Mylast, _Newvec);//移动,不能移动时拷贝
        } else { // provide basic guarantee
            _Umove(_Myfirst, _Whereptr, _Newvec);
            _Constructed_first = _Newvec;
            _Umove(_Whereptr, _Mylast, _Newvec + _Whereoff + 1);
        }
        _CATCH_ALL//拷贝出现异常的时候释放对应的内容
        _Destroy(_Constructed_first, _Constructed_last);
        _Al.deallocate(_Newvec, _Newcapacity);
        _RERAISE;
        _CATCH_END

        _Change_array(_Newvec, _Newsize, _Newcapacity);//更新数组信息
        return _Newvec + _Whereoff;//偏移到新元素的地址
    }

下面看扩展策略,传入的_Newsize是_Oldsize + 1,_Max是容器最大容量一般是达不到的我试着输出了一下我的环境下是4611686018427387903,可以看到正常情况下新的容量是以前的容量的1.5倍(不同编译器的扩容策略不一样,g++中是2),当新的容量还不够的时候会转而按需分配。

size_type _Calculate_growth(const size_type _Newsize) const {
        // given _Oldcapacity and _Newsize, calculate geometric growth
        const size_type _Oldcapacity = capacity();
        const auto _Max              = max_size();
        if (_Oldcapacity > _Max - _Oldcapacity / 2) {
            return _Max; // geometric growth would overflow
        }
        const size_type _Geometric = _Oldcapacity + _Oldcapacity / 2;
        if (_Geometric < _Newsize) {
            return _Newsize; // geometric growth would be insufficient
        }
        return _Geometric; // geometric growth is sufficient
    }

需要注意的是resize申请机制是按需分配的,当新的容量大于旧的时候会更新容量大小,当新的大小大于旧的容量的时候只会删除多余的元素而不会进行拷贝,数组容量不变不会释放数组空间但是多余的元素会被析构掉,详情运行下面这段代码。

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

class A {
public:
    A(){}
    int* a = new int(1);
    ~A() { cout << this << "析构"<<endl; }
};

void fun(vector<A>& vec) {
    vec.push_back(A());
    cout << &vec[0] << " " << vec.size() << " " << vec.capacity() << endl;
}
int main() {
    vector<A>vec;
    for (int i = 0; i < 10; i++) fun(vec);
    vec.resize(5);
    cout << vec.capacity()<<endl;
}

以上就是详解C++ STL中vector扩容机制的详细内容,更多关于C++ STL vector扩容的资料请关注程序员之家其它相关文章!

相关文章

  • c语言线程终止练习示例

    c语言线程终止练习示例

    这篇文章主要介绍了c语言线程终止练习示例,需要的朋友可以参考下
    2014-04-04
  • C++常见的stl容器与相关操作 示例解析

    C++常见的stl容器与相关操作 示例解析

    所谓容器,就是可以承载,包含元素的一个器件,它是STL六大组件之一,是容器、算法、迭代器中最重要也是最核心的一部分
    2022-10-10
  • C++内存管理介绍

    C++内存管理介绍

    这篇文章主要介绍了C++内存管理,C++标准委员会给我们提供了auto_ptr智能指针,后面又引入了share_ptr以及weak_ptr帮助我们正确和安全的使用指针,本文主要是介绍boost库提供的解决方案,需要的朋友可以参考一下
    2022-01-01
  • C语言猜凶手及类似题目的实现示例

    C语言猜凶手及类似题目的实现示例

    本文主要介绍了C语言猜凶手及类似题目的实现示例,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • c++非变易算法-stl算法

    c++非变易算法-stl算法

    本文主要介绍了C++ STL算法库中的非变易算法,是一些原则上不会变更操作数据的算法,包括:逐个查找算法、元素搜索算法、元素统计算法、序列匹配算法、子序列搜索算法、这些函数均包含于<algorithm>头文件,本文给出的所有代码在VS2010中编译运行通过
    2014-03-03
  • C语言中continue的用法详解

    C语言中continue的用法详解

    在C语言当中的continue和break语句是有一些类似的,但是它并不是强制进行终止的,下面这篇文章主要给大家介绍了关于C语言中continue用法的相关资料,需要的朋友可以参考下
    2022-11-11
  • QT中线程池QThreadPool类概念和使用方法详解

    QT中线程池QThreadPool类概念和使用方法详解

    这篇文章主要为大家介绍了QT中线程池QThreadPool类概念和使用方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • C语言关于include顺序不同导致编译结果不同的问题

    C语言关于include顺序不同导致编译结果不同的问题

    这篇文章主要介绍了在日常调试C语言中include的顺序不同从而影响最后编译结果不同的问题,究其原因是写代码的习惯所导致,下面跟小编一起来看看吧
    2022-04-04
  • C语言小知识之为什么要使用指针详析

    C语言小知识之为什么要使用指针详析

    指针是C语言中一个非常重要的概念,也是C语言的特色之一,使用指针可以对复杂数据进行处理,能对计算机的内存分配进行控制,在函数调用中使用指针还可以返回多个值,这篇文章主要给大家介绍了C语言小知识之为什么要使用指针的相关资料,需要的朋友可以参考下
    2021-10-10
  • C语言二叉排序树的创建,插入和删除

    C语言二叉排序树的创建,插入和删除

    本文主要介绍了Java实现二叉排序树的查找、插入、删除、遍历等内容。具有很好的参考价值,下面跟着小编一起来看下吧
    2021-10-10

最新评论

?


http://www.vxiaotou.com