C++ STL学习笔记(5) Vector容器, array容器,deque容器

5 篇文章 4 订阅

动态增长的数组vector,当它放入的元素满了的时候,会自动的扩充内存,但是,在计算机中内存不能够实现原地扩充,因为在申请了一块固定大小的内存之后,这块内存不管有没有用完,他后面的内存都有可能别的内容被占用。所以扩充的过程是在内存中别的地方找到一块足够大的内存,把原来的东西搬过去。

vector的内部示意图:

vector关键部分的实现代码: finish指向的是最后一个元素的后一个位置

可以先看一下vector定义的插入元素的方法,因为内存的扩容是发生在插入元素而内存不足的时候:

可以看到,在插入元素的时候,如果内存不足,会调用insert_aux的函数:

可以看到,insert_aux函数会开始的时候检查是否空间可用(很奇怪为什么到这里了还要检查空间是否够用,如果够用,则将元素继续插入到容器中,否则再次重新扩充内存,实际上,insert_aux函数不是是在这里被调用,它在其他地方也被调用,作为插入元素的函数,所以就会在刚开始的时候检查内存时候够用,这主要是针对别的地方调用它插入元素的时候使用的),可以看到,具体的内存扩充的代码在else语句里面,具体的实现如下所示:

所以在使用vector的时候,应该考虑到,在每次扩充内存的时候,会涉及到大量元素的拷贝动作,此时会调用拷贝构造函数,当拷贝完成后,释放掉原来的内存,需要同样调用析构函数,这是非常损耗性能的。

Vector的迭代器:

vector中迭代器的设计如下:

vector中迭代器的实现与前面讲的基本一样,也需要满足Iterator的一些基本的特征。

Array容器:

array本身就是C++种的数组,将它包装成容器,它就可以C++种容器的规律,比如可以提供迭代器,以便于让算法进行相应的操作,如果不将它数据进行这样的包装,数组就会被摈弃在STL的六大部件之外,失去了和这些部件之间的联系。

array的具体实现:

dequeue容器:
双向队列,内部的结构:

deque采用分段的方式(buffer),map指向的元素是一个指针,而这个指针指向的是buffer,所以map是一个只想指针的指针,存储map所指向的元素的容器是一个vector,。每一段buffer的元素满了的时候,deque会继续申请一块同样大小的内存,然后将内存的指针向前或者向后放入到map中,从而实现deque向前或者向后扩充。

deque的迭代器:

deque的迭代器有四个元素,cur, first,last, node, first指向内存段buffer的起始位置,last指向内存段buffer中最后一个元素的后一个位置(超尾),cur指向当前的元素,node指向当前buffer的指针, 也就是说指向vector中对应的指针。

可以看到dequeue的定义如下所示:

deque类中的定义的成员,可以看到map的类型,确实是指向指针的指针,还有迭代器start,  finish.,以及对应的begin(),end()方法。

下面可以看一下迭代器的具体实现,有助于理解上面的原理图:

可以看到迭代其中的四个元素,node的类型也可以知道,即node是指向指针的。

deque<T>::insert( )方法:

在deque中插入元素,deque会智能的通过插入的元素的位置而决定调用的方法:如果靠近头部,则会将插入元素到头部,如果靠近尾部,则会将元素插入尾部,否则,会采取另一种插入方法。

deque如何模拟连续空间:通过 deque iterator实现,可以看到deque内部定义的方法:

可以看到,size方法中,出现了两个迭代器相减,所以这个减法是一个重载的运算符,两个迭代器相减,返回荣光期内元素的个数,所以减号运算符的重载方法是:finish-start是map中元素的个数,而每个map中元素指向的buffer中有固定个数的元素(buffer size),所以可以根据这些值计算出容器中元素的个数。它的具体的实现如下所示:

其中,node是map种元素的指针(map种元素的内存是连续的)。所以(node - x.node - 1)就是map_size.

cur-first是当前迭代器种多出来的元素(没有放满一个buffer)

x.last-x.cur是另一个参与运算的迭代器中多出来的元素(没有放满一个buffer)

迭代器中++,--的实现:

// 前缀加
self& operator++()     
{
	++cur;    // 切换到下一个元素
	if (cur == last)   // 当前的buffer已经放满了
	{
		set_node(node + 1);     // 跳到下一个node 节点  map中的下一个元素
	}
	return *this;
}

// 后缀加
self& operator++(int dummy)
{
	self tmp = *this;
	++*this;       // 在后缀加中调用前缀加
	return tmp;
}

还有其他一些方法的实现,+=,-=等运算符的重载,

例如,在重载迭代器的+=运算符的时候,需要考虑到,每次加了之后,是否会超出当前的buffer,如果从超出,就需要切换到下一个node,具体的实现思路如下所示:

deque容器其他方法的实现也与此类似,都与要考虑到节点的切换问题:

容器,queue, stack

从上图可以看出queue,stack和deque的关系,在实现queue,stack的时候,不需要再重写一个queue和stack,我们只需让他们内含一个deque,然后再禁止deque中的某些方法即可,实现方法如下所示:

stack的实现:

 

-------------------------------------------------------end---------------------------------------------------------

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值