STL之vector的push_back过程详解

最近,被面试官的一道题问倒,很失落,明明看过《STL源码分析》,为啥这种问题还没答好,只能说自己看的时候没有仔细去思考。这道题就是标题的问题,面试完我重新看了一遍《STL源码分析》中关于这块的内容,这里记录下自己看完的一点理解。

在STL中,一般对容器的内存分配和构造是分开的2个过程,STL有专门的空间配置器负责分配内存,而构造则是通过placement new在已申请的内存上进行的,vector也不除外,下面是vector的push_back函数源码:

template <class T, class Alloc = alloc>
void vector::push_back(const T& x)
{
    if (finish != end_of_storage)
    {
        construct(finish, x);
        ++finish;
    }
    else
    {
        insert_aux(finish, x);
    }
}

其中,construct是STL的全局函数,是所有容器共用的,它的具体实现如下:

template<class T1, class T2>
inline void construct(T1* p, const T2& value)
{
    new(p) T1(value);    //placement new, 调用T1::T1(value);
}

而insert_aux是vector自己的成员函数,具体实现如下:

template <class T, class Alloc = alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x)
{
    //还有备用空间
    if (finish != end_of_storage)
    {
        //在备用空间起始处构造一个元素,并以vector最后一个值为其初值
        construct(finish, *(finish - 1));
        //调整水位
        ++finish;
        //拷贝一个元素
        T copy_x = x;
        //把vector插入位置position之后的元素往后移一位
        copy_backward(position, finish - 2, finish - 1);
        //给position指向的地方赋值,赋值内容为前面拷贝的元素
        *position = copy_x;
    }
    //已无备用空间
    else
    {
        const size_type old_size = size();
        //如果原来的vector为空,则申请一个元素的空间,否则申请可以容纳原来2倍元素的空间
        const size_type len = old_size == 0 ? 1 : 2 * old_size;
        //全局函数,申请空间
        iterator new_start = data_allocator::allocate(len);
        iterator new_finish = new_start;

        try
        {
            //将原来vector的position之前的内容拷贝到新的vector前面
            new_finish = uninitialized_copy(start, position, new_start);
            //调用构造函数为新插入的元素赋值
            construct(new_finish, x);
            //调整水位
            ++new_finish;
            //将原来vector的postion之后的内容拷贝到新的vector后面
            new_finish = uninitialized_copy(position, finish, new_finish);
        }
        catch (...)
        {
            //析构
            destroy(new_start, new_finish);
            //释放刚刚申请的内存
            data_allocator::deallocate(new_start, len);
            throw;
        }

        //析构原vecotr
        destroy(begin(), end());
        //释放原vector的空间
        deallocate();
        
        //调整迭代器指向新的vector
        start = new_start;
        finish = new_finish;
        end_of_storage = new_start +len;
    }
}

上面空间配置函数allocate的最底层实现比较复杂,但是只要记住2点即可,(1)如果申请的空间大于128字节,就直接调用malloc申请,(2)如果申请的空间小于等于128字节,就从STL维护的16条free list里面寻找合适的一块内存使用(这16条free list各自管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128字节的小额内存块,之所谓维护这些list既是为了防止频繁调用malloc,也是为了避免太多小额区块造成内存的碎片)。

介绍了这么多,回到最初的问题,如果我们一开始定义了一个空的vector,然后现在要往里面push_back一个个对象,这个过程具体是怎样的,请看下面这段代码的注释,这些都是我重新看了一遍书的理解:

//定义一个类A
class A{
    int x;
    double y;
};

//定义一个空的vector,vector中可以存放的是类A的对象
vector<A> vec;

//定义类A的对象a,对象b,对象c和对象d
A a;
A b;
A c;
A d;

/*
注意:根据上面对push_back的源码分析可知,
因为一开始vec是空的,所以会走insert_aux函数的无可用空间的分支,
调用allocate申请一块能够容纳《一》个A类对象的内存,
并调用拷贝构造函数把a赋值给vec的finish迭代器指向的内存,
说白了就是vec中存放的a和上面定义的a对象已经不是一个东西了。
*/
vec.push_back(a);

/*
注意:根据上面对push_back的源码分析可知,
现在vec也没有多余的可用空间,所以会再次走insert_aux函数的无可用空间的分支,
调用allocate申请一块能够容纳《两》个A类对象的内存,
把原来的vec的唯一一个元素a移动到新的vec上去,
并调用拷贝构造函数把b赋值给新的vec的finish迭代器指向的内存,这时a和b存放在相邻内存中。
*/
vec.push_back(b);

/*
注意:根据上面对push_back的源码分析可知,
现在vec也没有多余的可用空间,所以会再次走insert_aux函数的无可用空间的分支,
调用allocate申请一块能够容纳《四》个A类对象的内存,
把原来的vec的两个元素a和b移动到新的vec上去,
并调用拷贝构造函数把c赋值给新的vec的finish迭代器指向的内存,
这时a和b和c存放在相邻内存中,而且这块内存还能再容纳一个类A对象。
*/
vec.push_back(c);

/*注意:根据上面对push_back的源码分析可知,
现在vec还有一个可用空间,所以这次会走construct函数的分支,
通过placement new在已有的内存上调用拷贝构造函数把d赋值给finish迭代器指向的内存,
这时a和b和c和d存放在相邻内存中,但这块内存又再次没有剩余空间了。
*/
vec.push_back(d);

就写到这里吧,继续面壁思过了。

  • 20
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: vectorpush_back()是一个在vector的尾部加入一个元素的函数。例如: ``` #include <vector> #include <iostream> int main() { std::vector<int> vec; vec.push_back(1); // 在vec的尾部加入数字1 vec.push_back(2); // 在vec的尾部加入数字2 vec.push_back(3); // 在vec的尾部加入数字3 // 输出vec中的元素 for (int i = 0; i < vec.size(); i++) { std::cout << vec[i] << " "; } return 0; } ``` 输出: ``` 1 2 3 ``` push_back()函数还有一个重载版本,允许你在尾部加入多个元素。例如: ``` #include <vector> #include <iostream> int main() { std::vector<int> vec; vec.push_back(1); vec.push_back(2); vec.push_back(3); std::vector<int> other_vec; other_vec.push_back(4); other_vec.push_back(5); other_vec.push_back(6); vec.push_back(other_vec.begin(), other_vec.end()); // 输出vec中的元素 for (int i = 0; i < vec.size(); i++) { std::cout << vec[i] << " "; } return 0; } ``` 输出: ``` 1 2 3 4 5 6 ``` 在这个例子中,我们将other_vec中所有的元素一次性地加入到vec的尾部。 ### 回答2: vectorpush_back()函数是C++标准库中vector容器的一个成员函数,用于在vector的末尾插入一个新元素。该函数的参数是要插入的元素的值,可以是任意类型的数据。 使用push_back()函数,可以方便地将元素添加到vector容器中。当我们需要动态地向vector容器中添加元素时,可以使用该函数而不必事先指定容器的大小。 push_back()函数的工作原理是将元素添加到vector的末尾,并在必要时自动调整vector的大小以容纳新元素。如果vector的容量不足以容纳新元素,则会自动分配更大的内存间,并将原有元素复制到新的内存间中。 使用push_back()函数的一个常见应用场景是读取一系列输入数据并存储在vector中。我们可以使用循环结构来反复调用push_back()函数,直到读取完所有的输入数据为止。 总而言之,vectorpush_back()函数是一种方便实用的方法,可以动态地向vector容器中添加元素。它的使用方式简单,并且能够自动调整容器的大小以适应新元素的插入。 ### 回答3: vector是C++标准模板库(STL)中的容器之一,可以用于在运行时动态地存储和管理一组相同类型的数据。 push_back()是vector类中的一个成员函数,它用于向vector尾部插入一个元素。这个函数接受一个参数,即将要插入的元素的值,然后将该元素添加到vector的末尾。 使用push_back()函数可以动态地向vector中添加元素,无需事先知道vector的大小。当调用push_back()时,vector会自动扩展以容纳新的元素,并相应地更新其大小。 例如,如果我们有一个vector对象v,我们可以通过以下方式利用push_back()函数向其中添加元素: v.push_back(10); v.push_back(20); v.push_back(30); 上述代码会将整数10、20和30分别插入到vector v的尾部,结果v变为{10, 20, 30}。 需要注意的是,当向vector中插入元素时,vector可能会重新分配更大的内存间,以容纳更多的元素。因此,当在循环中多次使用push_back()函数插入大量元素时,可能会导致性能下降。为了减少这种情况的发生,可以在向vector中插入大量元素之前,使用reserve()函数预先分配足够的内存间。 总而言之,vectorpush_back()函数是一种方便的方式,用于将元素添加到vector的末尾,使其具备动态增长的能力,从而更好地满足程序的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值