10.6.3 std::deque
std::vector的特点是内存连续,好处是提供随机访问,坏处是除了在尾部插入或删除数据,在其他地方插入数据或删除数据,都很低效。
内存连续这个特性,当你需要时,比如要和纯C的裸指针对接,并且其他附加要求又逼着你用不了scoped_array, shared_array等工具,vector 就成了不二之选。、
内存连续也造成了没办法存储很多元素,特别是每个元素占用内存尺寸都比较大的情况下,使用vector保存就会发生程序卡的状态,甚至直接宣告失败(内存不足)。
可见,内存连续带来了“随机访问”的好处,但也带来前面提及的低性能的坏处。有没有折中办法呢?
比如,我们并不需要和纯C的裸指针对接(这种需求确实越来越少了),但是我们又需要“随机访问”,然后我们还希望插入及删除操作的性能能够有所改善……,这就是deques要做的是。
std::deque 是双向队列。
deque的用法:
#include <deque>
...
deque <int> de(5); //初始化含有5个元素
for(size_t i = 0; i < de.size(); ++i)
{
de[i] = i;
}
构造时,和vector一样,可以通过一个整数,指定初始化产生的默认元素个数。接着,演示为这5个元素赋值,使用的却是随机访问迭代。接着从前面一个“弹出”:
while(!de.empty())
{
/**< 返回队列前端第一个元素 */
int value = de.front();
cout << value << '.';
/**< 弹出(但并不返回)第一个数据 */
de.pop_front();
}
cout << endl;
front()返回队列前端第一个元素,不过调用它之前一定要先判断队列是否为空,front()和后面演示的back()都不会主动做越界检查;pop_front()就是弹出(但并不返回,即这个函数返回的是void)第一个数据(如果有)。
除了前面“弹出”,deque也支持从前面“压入”,对应方法是push_front();
for(size_t i = 0; i < 5; ++i)
{
/**< 从前面“压入”数据 *///队列中的数据为4, 3, 2, 1, 0
de.push_front(i);
}
做为对比,vector只提供front()读取操作,不提供push_front(), 尽管它肯定可以做到:调用insert()在第一个位置插入即是。
不直接提供“前插”操作是vector在暗示大家尽量不要对它做个这个操作,因为对vector来说,push_back()性能可能是不好,而push_front的性能肯定好不了。对于deque,push_front()也是一个高效的操作。
while(!de.empty())
{
int value = de.back();
cout << value << ',';
de.pop_back();
}
cout << endl;
for(size_t i = 0; i < 5; ++ i)
{
de.push_back(i);
}
尽管大家都有front()和back(),但相比vector,deque更需要二者,谁让它是一个“队列”呢?队列提供两端的操作是一种约定。其他从后面删除或插入,则与vector无异。
接下来,演示一下平凡无奇的前向迭代器和用于删除指定迭代器位置上的元素的erase()操作:
deque <int>::iterator it = de.begin();
advance(it, 2);
it = de.erase(it);
演示逆向迭代器在deque上的使用:
de.erase(it, de.end());
cout << "count after erase: "
<< de.size() << endl;
演示如何删除指定区间内的所有元素:
for(deque<int>::reverse_iterator rit = de.rbegin()
; rit != de.rend()
; ++ rit)
{
cout << * rit << ',';
}
cout << endl;
再次强调:访问元素时,除了at(index)会做检查越界,并在越界时抛出out_of_ranges异常之外,其他通过[ ]、front()、back()操作,都不做越界检查。不管是array,vector,还是这里的deque。
deque彻底自我管理预备内存空间,不提供reserve()和capacity()函数,
下面说一下deque内部如何管理内存空间。
首先明确一点:deque分配的内存,并不是完全连续的,所以肯定不能拿来和裸指针的接口对接。
deque会在内存中找出多块相对富余的内存块(我们称为“大块”),这些“大块”内存之间并不一定连续,但是每一“大块”内部的内存是连续的。当在某一“大块”内部进行随机访问时,它就是高效的随机访问。当需要夸块访问时,则需要额外的一点点附加的计算,具体计算过程由实作决定。
无论怎样,deque有办法保证它的随机访问总体上仍然高效。
了解这一特点可知:在deque前后两端插入或删除数据,都是高效的;在deque中间插入和删除数据,仍然和vector一样效率不高。
双向队列,重点保障就是两端的操作(如果非要频繁地在中间增删数据,那得用std::list)。再者,deque可以比vector更有利于存储数据量大的数。
那么,关于vector那个只涨不降的,让人不安的内存管理方法呢?这个deque没有特别要求,通常的实现,它是真正释放不用的内存的,当然得在它认为合适的时机。
【课堂作业】:“数组”,用什么好?
栈中分配的裸数组?堆中分配的裸数组?scoped_array? std::array? c11的unique_ptr <T[ ]>? std::vector?std::deque? 这些家伙都允许你使用[i]访问数据,是时候了,认真复习,对比,归纳,整理这些工具之间的同与异,长处短处,适用面,如何配合?