stl标准库系列之--deque

1、概述

deque 是 double-ended queue 的缩写,又称双端队列容器。是由一段一段的定量连续空间构成,可以向两端发展,因此不论在尾部或头部安插元素都十分迅速。 在中间部分安插元素则比较费时,因为必须移动其它元素。

我们看下deque的示意图。
stl标准库系列之--deque_第1张图片

2、与vector的区别

  • vector是单向开口的连续性空间,deque则是双向开口的连续空间(连续是假象)。
  • deque 容器也擅长在序列尾部添加或删除元素(时间复杂度为O(1)),而不擅长在序列中间添加或删除元素。
  • vector扩容时经历了申请空间、复制、释放原空间的过程,deque是在头或尾增加一端定量的连续空间,动态的以分段连续空间组合而成。
  • vector对头部的操作比较耗时,但deque擅长对头部元素进行操作,时间复杂度为O(1)。
  • vector存储的元素是在连续的空间中,deque不能保证连续空间。
  • deque的迭代器较vector迭代器来说复杂的多,因此,除非必要,请尽可能选择vector而非deque。

3、中控器的概念

上面我们看到了,deque与vector还是有明显的区别的。并且deque的连续空间是断章取义的。deque有一段一段的定量连续空间构成。如果在deque的头或者尾增加新空间。deque便会配置一段定量的连续空间,拼接在deque的头或者尾部。保证这些连续空间上的连续假象。避免和vector一样申请、复制、释放。代价是比较复杂的迭代器设计。

deque采取一块有连续空间的map(该map非stl中map)作为中控。该map的元素(我们可以看作是链表中的节点 - node),都是指针。指向另一段比较大的连续空间(称作缓冲区)。这些缓存区才是deque容器的存储主体。

简单来说,我们可以理解为:在内存中有大小相同、彼此不连续的 n 个空间(这些空间各自是连续的)。另外有一块连续的空间,这个空间里面保存了前面的那 n 个空间的指针。我们把这一块连续的空间,称作是deque的中控器。

举个简单的例子,我家有好多粮仓,但是了这些粮仓遍布在整个城市的各个区域,而在郊外的大别墅里面了,有一块地方保存着去这些粮仓的地图。

template <class _Tp, class _Alloc>
class _Deque_base {
public:
    typedef _Deque_iterator<_Tp,_Tp&,_Tp*>             iterator;
    typedef _Deque_iterator<_Tp,const _Tp&,const _Tp*> const_iterator;

protected:
    _Tp** _M_map;	//中控器
    size_t _M_map_size; 	//中控器的大小 
};

由上面的定义我们可以很明显的看出,map是一个指针,并且定义了size_t类型的变量来表示map的大小。我们再来看下map的初始化。

template <class _Tp, class _Alloc>
void _Deque_base<_Tp,_Alloc>::_M_initialize_map(size_t __num_elements)
{
    size_t __num_nodes = 
        __num_elements / __deque_buf_size(sizeof(_Tp)) + 1;

    _M_map_size = max((size_t) _S_initial_map_size, __num_nodes + 2);
    _M_map = _M_allocate_map(_M_map_size);

    _Tp** __nstart = _M_map + (_M_map_size - __num_nodes) / 2;
    _Tp** __nfinish = __nstart + __num_nodes;
        
    __STL_TRY {
        _M_create_nodes(__nstart, __nfinish);
    }
    __STL_UNWIND((_M_deallocate_map(_M_map, _M_map_size), 
                    _M_map = 0, _M_map_size = 0));
    _M_start._M_set_node(__nstart);
    _M_finish._M_set_node(__nfinish - 1);
    _M_start._M_cur = _M_start._M_first;
    _M_finish._M_cur = _M_finish._M_first +
                __num_elements % __deque_buf_size(sizeof(_Tp));
}

我们能够看到,首先,计算了有多少个node,然后再跟_S_initial_map_size比较取较大值。_S_initial_map_size是一个固定值 8 ,后续会说明。我们以 int 类型距离说明,通过计算,只有当入参__num_elements的值 ≥ 2560 时,map的大小才会大于8,否则map的初始大小均为 8 。

下面我们看一看中控器的图。
stl标准库系列之--deque_第2张图片

4、迭代器

前面我们已经说过了,deque和vector相似,但是它维护了表面上的空间连续,那么他是怎么维护的。deque的迭代器就会显得至关重要。而迭代器中的operator++和operator- -这两个运算子,承担起了这份责任。

  • 迭代器首先必须能够指出分段的连续空间在哪里,也就是我们说的缓冲区。
  • 其次需要能够判断是否已经在缓冲区的边缘。因为下一次的跳跃就得指向下一个或上一个缓冲区。

下面是deque迭代器的定义。

template <class _Tp, class _Ref, class _Ptr>
struct _Deque_iterator {
    typedef _Deque_iterator<_Tp, _Tp&, _Tp*>             iterator;
    typedef _Deque_iterator<_Tp, const _Tp&, const _Tp*> const_iterator;
    static size_t _S_buffer_size() { return __deque_buf_size(sizeof(_Tp)); }

    typedef random_access_iterator_tag iterator_category;
    typedef _Tp value_type;
    typedef _Ptr pointer;
    typedef _Ref reference;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    typedef _Tp** _Map_pointer;

    typedef _Deque_iterator _Self;

    _Tp* _M_cur;    //此迭代器所指缓冲区的current元素
    _Tp* _M_first;  //此迭代器所指缓冲区的头    使用push_front时,包含前面的空间
    _Tp* _M_last;   //此迭代器所指缓冲区的尾    包含未使用的空间
    _Map_pointer _M_node;   //指向中控器
    ...
}

从上面定义来看,一个迭代器里面包含了所指向缓冲区的头、尾、current元素以及这块缓冲区对应中控器中的节点。

这个迭代器内部对指针的操作都进行了重载操作,所以我们不能像对待vector迭代器那样来对待他。特别是当迭代器在一个缓冲区的边缘,下一次的跳跃会进行缓冲区的切换时,我们看下下面的函数。

void _M_set_node(_Map_pointer __new_node) {
	_M_node = __new_node;
	_M_first = *__new_node;
	_M_last = _M_first + difference_type(_S_buffer_size());
}

当进行node切换时,该迭代器的所指的缓冲区指向新的缓冲区,头指向新缓冲区的头,尾指向尾。
我们同时结合operator++或者operator- -操作来看会比较清楚一点。

_Self& operator++() {
    ++_M_cur;                       //切换至下一个元素
    if (_M_cur == _M_last) {        //如果已到达尾端
        _M_set_node(_M_node + 1);   //设置新的缓冲区,进行跳转
        _M_cur = _M_first;          //指向新缓冲区的第一个第一个元素
    }
    return *this; 
}
_Self operator++(int)  {    //后置
    _Self __tmp = *this;
    ++*this;
    return __tmp;
}

_Self& operator--() {
    if (_M_cur == _M_first) {
        _M_set_node(_M_node - 1);
        _M_cur = _M_last;       //需要注意的是,如果进行了减减,则current是先指向新缓冲区的尾,这个在后面我们验证一下
    }
    --_M_cur;
    return *this;
}
_Self operator--(int) {
    _Self __tmp = *this;
    --*this;
    return __tmp;
}

上面我们只是看了迭代在自增和自减时如果面临着缓冲区的跳跃时进行的转换,也就是单步跳跃,那么多步跳跃呢?

_Self& operator+=(difference_type __n)
{
    difference_type __offset = __n + (_M_cur - _M_first);
    if (__offset >= 0 && __offset < difference_type(_S_buffer_size()))
    _M_cur += __n;
    else {
    difference_type __node_offset =
        __offset > 0 ? __offset / difference_type(_S_buffer_size())
                : -difference_type((-__offset - 1) / _S_buffer_size()) - 1;
    _M_set_node(_M_node + __node_offset);
    _M_cur = _M_first + 
        (__offset - __node_offset * difference_type(_S_buffer_size()));
    }
    return *this;
}

由上面的定义我们可以看出,多步跳跃跟单步的是差不多的,值得注意的是,如果多步跳跃的步数比较大时,可能会进行map的跨越,而不单单是在同一个缓冲区中。

我们基本上已经全部看了迭代器的实现,但如果只有上面所说的这些鞋可能还是会比较迷糊,还是不能实际的理解,那么,下面我们通过一张图来看下迭代器长啥样。以便更好的理解。
stl标准库系列之--deque_第3张图片
由上图可以看出,我们画了两个迭代器,begin() 和 end() 函数返回的迭代器,如果最后一个缓冲区没有存满数据,则end()返回的迭代器能够指向他。

5、定义

在看deque容器的定义之前,我们先看看他的父类。

template <class _Tp, class _Alloc>
class _Deque_base {
public:
    typedef _Deque_iterator<_Tp,_Tp&,_Tp*>             iterator;
    typedef _Deque_iterator<_Tp,const _Tp&,const _Tp*> const_iterator;

    typedef _Alloc allocator_type;
    allocator_type get_allocator() const { return allocator_type(); }

    _Deque_base(const allocator_type&, size_t __num_elements)
        : _M_map(0), _M_map_size(0),  _M_start(), _M_finish() {
        _M_initialize_map(__num_elements);
    }
    _Deque_base(const allocator_type&)
        : _M_map(0), _M_map_size(0),  _M_start(), _M_finish() {}
    ~_Deque_base();    

protected:
    void _M_initialize_map(size_t);
    void _M_create_nodes(_Tp** __nstart, _Tp** __nfinish);
    void _M_destroy_nodes(_Tp** __nstart, _Tp** __nfinish);
    enum { _S_initial_map_size = 8 };   //使用枚举的方式可以在头文件中定义int类型的常量,初始化map的大小

protected:
    _Tp** _M_map;           //指向map,map是块连续空间,其中的每个元素都是一个指针(node),指向一块连续的内存空间(缓冲区)
    size_t _M_map_size;     //map的大小,初始值为0
    iterator _M_start;      //表现为第一个节点
    iterator _M_finish;     //表现为最后一个节点

    typedef simple_alloc<_Tp, _Alloc>  _Node_alloc_type;
    typedef simple_alloc<_Tp*, _Alloc> _Map_alloc_type;

    _Tp* _M_allocate_node()
        { return _Node_alloc_type::allocate(__deque_buf_size(sizeof(_Tp))); }
    void _M_deallocate_node(_Tp* __p)
        { _Node_alloc_type::deallocate(__p, __deque_buf_size(sizeof(_Tp))); }
    _Tp** _M_allocate_map(size_t __n) 
        { return _Map_alloc_type::allocate(__n); }
    void _M_deallocate_map(_Tp** __p, size_t __n) 
        { _Map_alloc_type::deallocate(__p, __n); }
};

没什么特别需要说明的,注意一下其中的注释就行了。

template <class _Tp, class _Alloc>
void _Deque_base<_Tp,_Alloc>::_M_destroy_nodes(_Tp** __nstart, _Tp** __nfinish)
{
    for (_Tp** __n = __nstart; __n < __nfinish; ++__n)
        _M_deallocate_node(*__n);
}

template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class deque : protected _Deque_base<_Tp, _Alloc> {

    // requirements:

    __STL_CLASS_REQUIRES(_Tp, _Assignable);

    typedef _Deque_base<_Tp, _Alloc> _Base;
public:                         // Basic types
    typedef _Tp value_type;
    typedef value_type* pointer;
    typedef const value_type* const_pointer;
    typedef value_type& reference;
    typedef const value_type& const_reference;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;

    typedef typename _Base::allocator_type allocator_type;
    allocator_type get_allocator() const { return _Base::get_allocator(); }

public:                         // Iterators
    typedef typename _Base::iterator       iterator;
    typedef typename _Base::const_iterator const_iterator;

#ifdef __STL_CLASS_PARTIAL_SPECIALIZATION
    typedef reverse_iterator<const_iterator> const_reverse_iterator;
    typedef reverse_iterator<iterator> reverse_iterator;
    #else /* __STL_CLASS_PARTIAL_SPECIALIZATION */
    typedef reverse_iterator<const_iterator, value_type, const_reference, difference_type> const_reverse_iterator;
    typedef reverse_iterator<iterator, value_type, reference, difference_type> reverse_iterator; 
#endif /* __STL_CLASS_PARTIAL_SPECIALIZATION */

protected:                      // Internal typedefs
    typedef pointer* _Map_pointer;
    static size_t _S_buffer_size() { return __deque_buf_size(sizeof(_Tp)); }

protected:
#ifdef __STL_USE_NAMESPACES
    using _Base::_M_initialize_map;
    using _Base::_M_create_nodes;
    using _Base::_M_destroy_nodes;
    using _Base::_M_allocate_node;
    using _Base::_M_deallocate_node;
    using _Base::_M_allocate_map;
    using _Base::_M_deallocate_map;

    using _Base::_M_map;
    using _Base::_M_map_size;
    using _Base::_M_start;
    using _Base::_M_finish;
#endif /* __STL_USE_NAMESPACES */

};

6、特点

当需要向容器的两边不停的添加或者删除元素的时候,应该使用deque。比如生产者和消费者中间的仓库。

7、创建方法

#include
using namespace std;

1、创建一个空的deque

deque(const allocator_type& __a = allocator_type()) : _Base(__a, 0) {}

定义一个元素为int类型的空deque。

std::deque<int> data;

这种方法跟vector、array等一样,在创建了空的deque之后,添加或者删除元素,这种方式比较常见。

2、创建一个含有 n 个元素的deque

deque(size_type __n) : _Base(allocator_type(), __n)
{ _M_fill_initialize(value_type()); } 
 std::deque<int> data(8);

创建了有8个元素的deque,其中每个元素的默认值均为0。

3、创建一个含有 n 个元素的deque,并为每个元素指定初始值

deque(size_type __n, const value_type& __value, const allocator_type& __a = allocator_type()) : _Base(__a, __n)
{ _M_fill_initialize(__value); }
std::deque<int> d(8, 5);

创建了有8个元素的deque,其中每个元素的初始值均为5。

4、通过拷贝一个deque来创建一个新的deque

deque(const deque& __x) : _Base(__x.get_allocator(), __x.size()) 
{ uninitialized_copy(__x.begin(), __x.end(), _M_start); }
std::deque<int> data(8);
std::deque<int> data1(data);

拷贝data创建新的deque data1。这种方式下,新旧deque容器的元素类型必须保持一致。

5、通过拷贝其他类型的容器来创建deque

deque(const value_type* __first, const value_type* __last,
const allocator_type& __a = allocator_type()) 
: _Base(__a, __last - __first)
{ uninitialized_copy(__first, __last, _M_start); }

deque(const_iterator __first, const_iterator __last,
const allocator_type& __a = allocator_type()) 
: _Base(__a, __last - __first)
{ uninitialized_copy(__first, __last, _M_start); }
int array[] = {1, 2, 3, 4};
std::deque<int> data4(array, array + 4);
vector<int> primes {2, 3, 5, 7, 11, 13, 17, 19};
std::deque<int> data5(++primes.begin(), --primes.end());

6、赋值

deque& operator= (const deque& __x) {
    const size_type __len = size();
    if (&__x != this) {
    if (__len >= __x.size())
        erase(copy(__x.begin(), __x.end(), _M_start), _M_finish);
    else {
        const_iterator __mid = __x.begin() + difference_type(__len);
        copy(__x.begin(), __mid, _M_start);
        insert(_M_finish, __mid, __x.end());
    }
}
std::deque<int> data(8);
std::deque<int> data1 = data;

下面我们通过一个例子来看看上面的这些情况。

#include 
#include 
#include 

using namespace std;

int main(int argc, char* argv[])
{
    deque<double> data;     //创建一个double 类型的空deque容器
    cout << data.size() << " " << data.max_size() << endl;  //0 2305843009213693951

    deque<int> data1(10);   //创建一个int类型的有10个元素deque容器,每个元素默认为0
    cout << data1.size() << " " << data1.max_size() << endl;    //10 4611686018427387903
    for(deque<int>::iterator itor = data1.begin(); itor != data1.end(); ++itor)
    {
        cout << *itor << " ";   //0 0 0 0 0 0 0 0 0 0
    }
    cout << endl << data1.begin()._S_buffer_size() << endl; //128   现在的版本已经对缓冲区这块做了修改,定量大小为 512byte

    for(int nIndex = 1; nIndex <= 130; ++nIndex)
    {
        data.push_back(nIndex);
    }

    for(deque<double>::iterator itor = data.begin(); itor != data.end(); ++itor)
    {
        cout << *itor << " ";   // 1 2 3 ... 128 129 130
    }
    cout << endl << (++data.begin())._S_buffer_size()  << " data size : " << data.size() << endl; //64 data size : 130

    cout << *(data.begin()._M_first) << " " << *(data.begin()._M_last - 1) << endl;     // 1 64     first cache

    cout << *(data.end()._M_first) << " " << *(data.end()._M_cur - 1) << " " << *(data.end()._M_last - 1) << endl; //129 130 0

    cout << data.begin()._M_node << " " << data.end()._M_node << endl;  //0x1a1f48 0x1a1f58 //实际上被用掉的map的长度 相减为十六进制10 即16,两个double长度然后加end,map实际使用 3

    deque<int> data2(10, 4);   //创建一个int类型的有10个元素deque容器,每个元素默认为4
    for(deque<int>::iterator itor = data2.begin(); itor != data2.end(); ++itor)
    {
        cout << *itor << " ";   //4 4 4 4 4 4 4 4 4 4
    }
    cout << endl;

    deque<int> data3(data2);
    for(deque<int>::iterator itor = data3.begin(); itor != data3.end(); ++itor)
    {
        cout << *itor << " ";   //4 4 4 4 4 4 4 4 4 4
    }
    cout << endl;

    int array[] = {1, 2, 3, 4};
    deque<int> data4(array, array + 4);
    for(deque<int>::iterator itor = data4.begin(); itor != data4.end(); ++itor)
    {
        cout << *itor << " ";   //1 2 3 4
    }
    cout << endl;

    vector<int> primes {2, 3, 5, 7, 11, 13, 17, 19};
    deque<int> data5(++primes.begin(), --primes.end());
    for(deque<int>::iterator itor = data5.begin(); itor != data5.end(); ++itor)
    {
        cout << *itor << " ";   //3 5 7 11 13 17
    }
    cout << endl;

    deque<int> data6 = data4;
    for(deque<int>::iterator itor = data6.begin(); itor != data6.end(); ++itor)
    {
        cout << *itor << " ";   //1 2 3 4
    }
    cout << endl;

    return 0;
}

运行结果如下:
stl标准库系列之--deque_第4张图片
我们针对上面的例子,对其中的一些数据说明一下。
首先,我们看下deque中每个缓冲区的大小。先看下源码中的函数:

inline size_t __deque_buf_size(size_t __size) {
	return __size < 512 ? size_t(512 / __size) : size_t(1);
}

由上面的函数我们能够看出,当__size < 512 时,缓冲区的大小为512byte。再查看源码中调用该函数的地方,发现,该函数的入参时固定的,为 sizeof(_Tp);也就是说,现在的stl版本中,已经固定了deque的缓冲区大小为512byte。通过上面的例子我们也得到了验证。

cout << endl << data1.begin()._S_buffer_size() << endl; //128   现在的版本已经对缓冲区这块做了修改,定量大小为 512byte

然后我们再看下一个迭代器中的,也就是一个缓存区的大小和deque的大小。这一点在上面的迭代器这一节中我们已经有所了解了。

cout << endl << (++data.begin())._S_buffer_size()  << " data size : " << data.size() << endl; //64 data size : 130

接下来我们看下data.begin()data.end()这两个迭代器中的数据存储规律,我们通过打印发现:

cout << *(data.begin()._M_first) << " " << *(data.begin()._M_last - 1) << endl;     // 1 64     first cache
cout << *(data.end()._M_first) << " " << *(data.end()._M_cur - 1) << " " << *(data.end()._M_last - 1) << endl; //129 130 0

计算一下,data的size是130,一个缓冲区中能存放64个double类型数据,那也就是说,data.end()这个迭代器中的数据只有两个,看下上面的打印就能知道。这样的结果也是我们验证上面迭代器中迭代器的相关属性。

最后我们看下data这个deque中的map使用情况,我们在上面知道了,deque data中map的大小为8,我们通过打印data.begin()和data.end()中node的地址来计算map的实际使用量。如下,相减为十六进制10 即16,两个double长度(准确的来说,是两个指针的大小,因为我的系统是64位的,并且deque里面的map存放的是_Tp **的指针,指向每一个缓冲区),然后再加上end()的使用,map实际使用大小为3。

cout << data.begin()._M_node << " " << data.end()._M_node << endl;  //0x1a1f48 0x1a1f58 //实际上被用掉的map的长度

8、map的空间扩充

我们来通过一个例子看一下map是怎么进行空间扩充的。实践是学东西的一个很好的方法,以后有不懂的时候,动手实践下可能就会了。

#include 
#include 
#include 

using namespace std;

int main(int argc, char* argv[])
{
    deque<double> data;     //创建一个double 类型的空deque容器

    for(int nIndex = 1; nIndex <= 511; ++nIndex)
    {
        data.push_back(nIndex);
    }

    cout << *(data.begin()._M_first) << " " << *(data.begin()._M_last - 1) << " " << data.size() << endl;   //1 64 511

    cout << *((--data.end())._M_first) << " " << *((--data.end())._M_cur) << " " << *((--data.end())._M_last - 1) << endl;  //449 511 0

    cout << data.begin()._M_node << " " << data.end()._M_node << endl;  //0x811fd0 0x812008

    data.push_back(512);
    data.push_back(513);

    cout << data.begin()._M_node << " " << data.end()._M_node << endl;  //0x811fd0 0x812010

    data.push_front(514);
    data.push_front(515);

    cout << *(data.begin()._M_cur) << " " << *(data.begin()._M_last - 1) << endl;   //515 514

    cout << *(data.end()._M_first) << " " << *(data.end()._M_cur - 1) << " " << *(data.end()._M_last - 1) << endl;  //513 513 0

    cout << data.begin()._M_node << " " << data.end()._M_node << endl;  //0x811fc8 0x812010

    return 0;
}

运行结果:
stl标准库系列之--deque_第5张图片

通过上面的例子可以看到,我们首先给data里面push_back 511 个元素,刚好在最后一个map未被使用,此时记录data.begin()data.end()包含的node的地址。值为:0x811fd0 0x812008 两者相差56,为7个double类型的空间,然后给data里面再push_back两个元素。记录data.begin()和data.end()包含的node的地址,值为:0x811fd0 0x812010,发现begin()的值未变,但是end()的值增加了8。其实只push_back一个元素也是同样的结果。因为在push元素之后,发现改缓冲区已经没有空间了,于是申请了下一段连续的空间(缓冲区)。

因此,我们可以得出:deque中map的空间进行扩充使用的是移动构造,并非是拷贝构造,跟vector的空间扩充是有区别的。

移动构造:移动构造函数是c++11的新特性,移动构造函数传入的参数是一个右值 用&&标出。一般来说左值可以通过使用std:move方法强制转换为右值。移动构造函数首先将传递参数的内存地址空间接管,然后将内部所有指针设置为nullptr,并且在原地址上进行新对象的构造,最后调用原对象的的析构函数,这样做既不会产生额外的拷贝开销,也不会给新对象分配内存空间。

拷贝构造:申请->复制->释放原空间。拷贝构造函数是先将传入的参数对象进行一次深拷贝,再传给新对象。这就会有一次拷贝对象的开销,并且进行了深拷贝,就需要给对象分配地址空间。

移动构造函数就是为了解决这个拷贝开销而产生的。

9、操作数据的方法

1、重载运算符[]

deque<int> data(5, 2);

int a = data[3];    //2
int b = data[5];    //0
int c = data[128];    //错误

可以看到,容器名[n]的这种方式,不仅可以访问容器中的元素,还可以对其进行修改。但需要注意的是,使用此方法需确保下标 n 的值不会超过容器中存储元素的个数,否则会发生越界访问的错误。

同时,如果某个缓冲区有未使用的空间,访问这些空间不会造成程序错误,只会返回一个不确定的值。

2、at(int pos)

有效地避免越界访问,由于该函数会返回容器中指定位置处元素的引用形式,因此利用该函数的返回值,既可以访问指定位置处的元素,如果需要还可以对其进行修改。

at() 成员函数会自行判定访问位置是否越界,如果越界则抛出std::out_of_range异常。

deque<int> data(5, 2);
std::cout << data.at(2) << endl;

//std::cout << data.at(5) << endl;	std::out_of_range

3、front()和back()

它们分别返回 deque容器中第一个和最后一个元素的引用,通过利用它们的返回值,可以访问(甚至修改)容器中的首尾元素。

deque<int> data(5, 2);
std::cout << data.front() << endl;
std::cout << data.back() << endl;

3、push、pop、insert

成员函数 功能
push_back() 在容器现有元素的尾部添加一个元素
pop_back() 移除容器尾部的一个元素
push_front() 在容器现有元素的头部添加一个元素
pop_front() 移除容器尾部的一个元素
emplace_back() C++11 新添加的成员函数,其功能是在容器尾部生成一个元素
emplace_front() C++ 11 新添加的成员函数,其功能是在容器头部生成一个元素
insert() 在指定的位置直接生成一个元素。
emplace() C++ 11 新添加的成员函数,其功能是 insert() 相同,即在指定的位置直接生成一个元素。

push_back() 和push_front() 都是给容器中插入数据,不同的事一个是插在容器的尾部,一个则是插在容器的头部。

void push_back(const value_type& __t) {
	if (_M_finish._M_cur != _M_finish._M_last - 1) {
		construct(_M_finish._M_cur, __t);
		++_M_finish._M_cur;
	}
	else
		_M_push_back_aux(__t);
}

void push_back() {
	if (_M_finish._M_cur != _M_finish._M_last - 1) {
		construct(_M_finish._M_cur);
		++_M_finish._M_cur;
	}
	else
		_M_push_back_aux();
}

上面是push_back函数的原型。push_front则差不多的。

insert方法的实现跟其他容器的都差不多。主要有4种方法。

语法格式 功能
iterator insert(pos, elem) 在迭代器 pos 指定的位置之前插入一个新元素elem,并返回表示新插入元素位置的迭代器。
iterator insert(pos, n, elem) 在迭代器 pos 指定的位置之前插入 n 个元素 elem,并返回表示第一个新插入元素位置的迭代器。
iterator insert(pos, first, last) 在迭代器 pos 指定的位置之前,插入其他容器(不仅限于vector)中位于 [first,last) 区域的所有元素,并返回表示第一个新插入元素位置的迭代器。
iterator insert(pos, initlist) 在迭代器 pos 指定的位置之前,插入初始化列表中所有的元素,并返回表示第一个新插入元素位置的迭代器。
std::deque<int> data {1, 2};
//第一种格式用法
data.insert(data.begin() + 1, 3);   //{1, 3, 2}

//第二种格式用法
data.insert(data.end(), 2, 5);  //{1, 3, 2, 5, 5}

//第三种格式用法
std::array<int, 3> array{ 7, 8, 9 };
data.insert(data.end(), array.begin(), array.end());    //{1, 3, 2, 5, 5, 7, 8, 9 }

//第四种格式用法
data.insert(data.end(), { 10,11 }); //{1, 3, 2, 5, 5, 7, 8, 9, 10, 11 }

4、为什么要有emplace系列的函数

emplace系列的函数是在C++11之后才添加的,主要是因为引入了移动构造可以减少开销。具体的内容与std::map的emplace系列函数是一样的操作。测试用例也差不多。如果有兴趣,请看上一篇《两小时带你从0学习stl库–map》第九节并自行测试。

你可能感兴趣的:(#,STL标准模版库,stl,deque,模版库,源码)