STL之vector扩容机制

本文详细解析了C++中vector容器在空间不足时的push_back操作,涉及vector的扩容机制及其效率问题。文章指出,不同编译器如VS和GCC在扩容策略上存在差异,VS采用1.5倍扩容,而GCC采用2.0倍。扩容因子的选择影响内存利用率和分配耗时。此外,还介绍了size()、capacity()、resize()和reserve()等关键函数的使用,并讨论了扩容后数据地址的变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

    大家好,我是萝卜。上期结尾说到vector的push_back操作一般情况下时间复杂度为O(1),是否存在特殊情况。那么本期就讲讲vector在容器空间不足时进行push_back操作会发生什么。

    vector作为STL的常用容器之一,其特性和数组类似,拥有一段连续的内存空间。vector申请的是一段连续的内存,当插入新的元素内存不够时,通常会再重新申请更大的一块内存,将原来的元素拷贝过去,释放旧空间。因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为O(n)。

相关函数

在讲解vector扩容机制前,先了解四个函数:

size()capacity()resize()reserve()

  • size():

size()函数返回当前vector所容纳元素的数目,即使用的空间大小。

  • capacity()

capacity()函数返回当前vector在重新进行内存分配以前所能容纳的元素数量,即返回的是总的容量大小,capacity()-size()后就是未使用的空间大小。

使用者可以通过reserve()来改变capacity(),resize()改变size()。

  • resize()

resize,即重置容器空间。当设置值小于当前容器空间时,会将目前容器中超出设置值的空间释放掉;当设置值大于当前容器空间时,会在当前空间的基础上增加容量。

举个例子,vector当前容量为10,若使用resize(20)设置容量为20,则需要再扩容增加10个;若使用resize(5)设置容量为5,则将6-10的空间进行释放。

源码如下:

void resize(size_type __new_size){
    if (__new_size > size()){ 
        _M_default_append(__new_size - size());
    }
    else if (__new_size < size()){
        _M_erase_at_end(this->_M_impl._M_start + __new_size);
    }
}
  • reserve()

reserve,即预留容器空间。当设置值大于当前容器空间时,会增加当前容器空间的大小,源码如下:

void reserve(size_type __n){
    if (__n > max_size()){
        __throw_length_error(__N("vector::reserve"));
    }
    if (capacity() < __n){
        _M_reallocate(__n);
    }
}

扩容机制

不同编译器在vector的扩容策略上显然不太一致,在vector的size()(当前容器所用空间)等于capacity()(当前容器总空间)时会发生扩容。

不同的的编译器实现方式不同,vs编译器每次是以1.5倍且向下取整的策略进行扩容,gcc编译器则是每次以2.0倍的策略进行扩容。

扩容因子的比较

扩容的大小叫做扩容因子,扩容因子由编译器决定,VS的扩容因子为1.5,G++中,扩容因子为2。

  • 扩容因子大,每次需要分配的新内存空间越多,分配空间耗时。空闲空间较多,内存利用率低。

  • 扩容因子小,需要再分配的可能性更高,扩容耗时。空闲空间较少,内存利用率较。

一般认为扩容因子1.5优于2.0,原因是以1.5作为扩容因子可以实现复用释放的内存空间。

以2为扩容因子时,空闲的空间始终要小于需要分配的空间

以1.5为扩容因子时,随着分配空间增大,之前释放的空闲空间能够满足当次扩容所需的空间,实现内存的复用

扩容后数据地址变化

在扩容时,系统会选择一端更大的内存,将数据从原来的内存拷贝过来,同时释放原有的内存。这时数据存在的地址就发生了改变。我们可以通过&vector[0]的方式来查看数据首地址。

注意:对于vector,&vector和&vector[0]是不一样的; 而对于数组,&array与&array[0]是一样的。

<think>好的,我需要回答用户关于C++ vector扩容机制的问题,包括内部实现和源码分析。首先,用户提供的引用中有几个相关的资料,比如引用[2]、[4]、[5]提到了扩容机制,特别是引用[2]中的C++代码示例和扩容时的容量变化,引用[4]和[5]讨论了无参构造器和扩容过程。需要注意的是,用户可能混淆了C++vector和Java的Vector,因为引用[4]和[5]明显是关于Java的,但用户的问题是关于C++的,所以应该主要参考引用[2]和[3]。 首先,我需要整理C++ vector扩容机制。根据STL的实现,当vector的size达到capacity时,会分配更大的内存空间,通常是原容量的1.5倍或2倍(不同编译器可能不同,比如VS是1.5倍,GCC是2倍)。然后将旧元素拷贝到新空间,释放旧内存。这个过程会影响性能,因此reserve预分配可以优化。 接下来,需要分析源码。例如,push_back操作会检查是否需要扩容,调用reserve或者相关的函数。在扩容时,可能会调用_M_realloc_insert函数(以GCC为例),其中计算新容量,分配内存,移动元素。这部分需要结合源码结构来解释,比如指针如何管理,迭代器失效的问题。 另外,用户提供的引用[2]中的代码示例展示了扩容时地址的变化,说明内存重新分配,这可以用来举例说明扩容发生的情况。比如,当容量不足时,vec.push_back(1)会导致vector扩容,导致存储地址改变。 需要注意的是,C++和Java的扩容机制不同,比如Java的Vector默认增长一倍,而C++的实现依赖于编译器,所以需要明确区分。在回答中应该指出这一点,避免混淆。 然后,生成相关问题,比如扩容策略的不同,性能影响,如何避免频繁扩容等。这些问题可以帮助用户进一步理解。 最后,确保回答结构清晰,分步骤解释扩容机制,结合源码和示例,引用正确的资料(如引用[2]和[3]),并正确标注引用标记。</think>### C++ vector扩容机制解析 #### 一、核心扩容机制 C++ STLvector扩容机制遵循以下步骤: 1. **容量检查**:当插入元素时(如`push_back`),若当前`size() == capacity()`,触发扩容。 2. **新容量计算**:不同编译器的扩容策略不同: - **GCC**:新容量 = 原容量 × 2 - **MSVC**:新容量 = 原容量 × 1.5 3. **内存重分配**:分配新的连续内存空间,大小等于新容量。 4. **元素迁移**:将旧元素全部拷贝/移动到新内存(C++11后优先使用移动语义)。 5. **旧内存释放**:销毁旧元素并释放原内存块[^2][^3]。 #### 二、源码实现分析(以GCC为例) 关键函数`push_back`调用链: ```cpp // vector.tcc template<typename _Tp, typename _Alloc> void vector<_Tp, _Alloc>::push_back(const value_type& __x) { if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage) { // 直接构造元素 } else _M_realloc_insert(end(), __x); // 触发扩容 } // 扩容函数实现 template<typename _Tp, typename _Alloc> void vector<_Tp, _Alloc>::_M_realloc_insert(iterator __position, const _Tp& __x) { const size_type __len = _M_check_len(1, "vector::_M_realloc_insert"); pointer __new_start = _M_allocate(__len); // 分配新内存 // 迁移元素并插入新元素 _M_deallocate(this->_M_impl._M_start, this->_M_impl._M_end_of_storage - this->_M_impl._M_start); // 释放旧内存 } ``` 其中`_M_check_len`计算新容量: ```cpp size_type _M_check_len(size_type __n, const char* __s) const { if (max_size() - size() < __n) __throw_length_error(__s); const size_type __len = size() + std::max(size(), __n); // GCC采用2倍扩容策略 return (__len < size() || __len > max_size()) ? max_size() : __len; } ``` #### 三、示例验证 引用[2]中的代码在VS环境下运行后输出: ``` 0x1e4b668 1 1 0x1e4b668 2 2 0x1e4b668 3 3 ...(前三次扩容后地址变化) 0x1e4b6a8 4 6 // 容量从3扩容到6(1.5倍) ``` 表明MSVC采用1.5倍扩容策略,且每次扩容后内存地址改变。 #### 四、性能优化建议 1. **预分配内存**:使用`reserve(n)`预先分配足够容量,避免多次扩容。 2. **移动语义**:C++11后优先使用`emplace_back`减少拷贝开销。 3. **迭代器失效**:扩容后原有迭代器、指针、引用均失效[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萝卜说菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值