概述:
上一篇我们讲过queue容器,它是一个单向的队列,只能从尾部插入,头部删除。而本节讲的deque容器,它是一个双向的队列,在头尾均可以调用插入与删除,并且支持迭代器和迭代器随机访问,这是它与queue的最大区别。
实际上,deque容器的函数特性和vector容器更像,或者是几乎一样,即使queue和deque
都是队列,但实际上除了可以满足先进先出共性不大,因为queue不支持迭代器等操作。
下面给出关于vector容器的文章。对于我个人而言或者比较熟STL的人,可以只看两篇文章的总结。
https://blog.csdn.net/weixin_44517656/article/details/110292591
1 deque容器的遍历
非常简单,同样是使用迭代器遍历即可。
void printDeque(const deque<int>&d){
// iterator 普通迭代器
// reverse_iterator 反转迭代器
// const_iterator 只读迭代器
for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++){
//*it = 10000; 只读,不能修改
cout << *it << " ";
//it=it+1; 支持迭代器随机访问
}
cout << endl;
}
2 deque的插入
2.1 简单使用deque的插入
void test01(){
deque<int>d;
//尾插
d.push_back(10);
d.push_back(20);
d.push_back(30);
//头插
d.push_front(100);
d.push_front(200);
d.push_front(300);
//insert插入
d.insert(d.begin(), 400);
printDeque(d);
}
结果:
2.2 测试deque容器的插入是否会造成迭代器失效
2.2.1 先测试push_back
测试代码。
void test02() {
deque<int>d;
//尾插
d.push_back(10);
d.push_back(20);
d.push_back(30);
//头插
d.push_front(100);
d.push_front(200);
d.push_front(300);
//d.resize(10);
for (auto it = d.begin(); it != d.end(); it++) {
if (*it == 200) {
d.push_back(150);
}
}
printDeque(d);
}
1)先获取迭代器首地址。
2)调用push_back函数后,虽然地址没有改变,但是系统会认为该片内存被修改过(插入元素了),将该迭代器置为非法,所以再使用就会报错,类似vector一样。
并且我也尝试在插入前给队列足够的空间即resize(10),防止内存不够而新开辟内存,但是仍然会报错,即无论是否内存足够,deque的插入操作都会造成迭代器失效,更深一层分析是因为它像vector一样,建立的方式为数组,地址是连续的,所以插入会失效。而list不会,因为list不是数组,地址不连续,地址是动态开辟的。
2.2.2 测试push_front
代码在测试push_back的if条件中改成push_front即可。即:
if (*it == 200) {
//d.push_back(150);
d.push_front(250);
}
1)先获取迭代器首地址。
2)调用push_front函数后,和调用push_back函数后的结果一样,同样会被系统置为非法,继续使用会造成迭代器失效。
2.2.3 测试insert
同样改一下上面代码即可。
if (*it == 200) {
//d.push_back(150);
//d.push_front(250);
d.insert(it, 150);
}
1)先获取迭代器首地址。
2)调用insert函数后,和调用push_back,push_front函数后的结果一样,同样也会被系统置为非法,继续使用会造成迭代器失效。
2.3 总结deque容器的插入
- 1)不管deque容器的内存是否足够,凡是调用了push_back,push_front,insert插入函数,都会造成迭代器失效。原因是因为使用数组创建,插入时内存地址是连续的,容易造成数据访问混乱,并且当内存不足时会被拷贝到新的内存地址中,故被系统标记为非法。类似vector。
- 2)区别于list,因为list不是数组创建的,它的内存地址不是连续的,故调用push_back,push_front,insert这些插入函数时迭代器不会失效。
3 deque容器的删除
3.1 简单使用deque容器的删除
void test03() {
deque<int>d;
//尾插
d.push_back(10);
d.push_back(20);
d.push_back(30);
//头插
d.push_front(100);
d.push_front(200);
d.push_front(300);
/*因为我删除元素后并未继续使用迭代器,所以不需担心迭代器是否失效*/
d.pop_back();
d.pop_front();
d.erase(d.begin());
printDeque(d);
}
结果:
3.2 测试deque容器的删除是否会造成迭代器失效
3.2.1 测试deque容器的pop_back删除
1)先获取迭代器首地址。
2)调用pop_back函数后,可以看到迭代器没有改变,并且程序是能正常运行的,这是因为pop_back的删除并非删除地址未对内存地址做出改变,而是通过元素的前后移动进行删除的,所以调用pop_back后迭代器没有失效。
能正确打印结果:
3.2.2 测试deque容器的pop_front删除
1)先获取迭代器首地址。
调用pop_front函数后,迭代器地址未改变,并且继续运行程序也是正常的,说明迭代器并未失效。
3.2.3 测试deque容器的erase删除
测试代码同样改一下即可。
if (*it == 200) {
//d.pop_back();
//d.pop_front();
d.erase(it);
}
1)先获取迭代器首地址。
2)调用erase函数后,虽然迭代器地址仍一样,但是该地址已经改变了,所以再次使用该迭代器会报错,也就是说调用erase会使迭代器失效。
这里需要提醒一下:deque的pop_front,pop_back这些函数不会回收地址,而只是通过前后移动进行删除。而erase这个函数是会可以认为系统会回收地址导致迭代器失效不能再访问该内存地址。
3.3 总结deque的删除
- 1)调用pop_back,pop_front函数后,迭代器地址未改变,并且继续运行程序也是正常的,说明迭代器并未失效。这是因为pop_back的删除并非删除地址并未对内存做出改变,而是通过元素的前后移动进行删除的,所以调用pop_back后迭代器没有失效。
- 2)而调用erase函数删除后,虽然迭代器地址仍一样,但是该地址已经改变了,所以再次使用该迭代器会报错,也就是说调用erase会使迭代器失效。
这里需要提醒一下:deque的pop_front,pop_back这些函数不会回收地址,而只是通过前后移动进行删除。而erase这个函数是会可以认为系统会回收地址导致迭代器失效不能再访问该内存地址。
4 总结deque的插入和删除
4.1 插入
- 1)不管deque容器的内存是否足够(可以resize测试),凡是调用了push_back,push_front,insert插入函数,都会造成迭代器失效。原因是因为使用数组创建,插入时内存地址是连续的,容易造成数据访问混乱,并且当内存不足时会被拷贝到新的内存地址中,或者换种说话是deque的插入改变了内存地址结构,故被系统标记为非法。类似vector。
- 2)区别于list,因为list不是数组创建的,它的内存地址不是连续的,故调用push_back,push_front,insert这些插入函数时迭代器不会失效。
4.2 删除
- 1)调用pop_back,pop_front函数后,迭代器地址未改变,并且继续运行程序也是正常的,说明迭代器并未失效。这是因为pop_back的删除并非删除地址并未对内存做出改变,而是通过元素的前后移动进行删除的,所以调用pop_back后迭代器没有失效。(vector调用pop后继续使用迭代器也是不会报错的)
- 2)而调用erase函数删除后,虽然迭代器地址仍一样,但是该地址已经改变了,所以再次使用该迭代器会报错,也就是说调用erase会使迭代器失效。
这里需要提醒一下:deque的pop_front,pop_back这些函数不会回收地址,而只是通过前后移动进行删除。而erase这个函数是会可以认为系统会回收地址导致迭代器失效不能再访问该内存地址。
5 deque容器其它成员函数
5.1 deque构造函数
由于这些比较简单,直接伪代码了。
deque<T> deqT;//默认构造形式
deque(beg, end);//构造函数将[beg, end)区间中的元素拷贝给本身。
deque(n, elem);//构造函数将n个elem拷贝给本身。
deque(const deque &deq);//拷贝构造函数。
5.2 deque赋值操作
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
deque& operator=(const deque &deq); //重载等号操作符
swap(deq);// 将deq与本身的元素互换
5.3 deque大小操作
deque.size();//返回容器中元素的个数
deque.empty();//判断容器是否为空
deque.resize(num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
deque.resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除。
5.4 algorithm自带的算法sort排序
void test05(){
//利用sort排序
deque<int>d;
d.push_back(10);
d.push_back(20);
d.push_back(30);
d.push_back(40);
d.push_front(100);
d.push_front(200);
d.push_front(300);
d.push_front(400);
//默认排序规则从小到大 算法里的sort()只支持随机访问的.故List自带sort()
sort(d.begin(), d.end());
printDeque(d);
//从大到小排序
sort(d.begin(), d.end(), [&](int a, int b) {return a > b; });
printDeque(d);
}
结果。
6 关于deque的多线程测试
这里不再测试deque容器的多线程测试,因为知道这个容器的插入和删除相关函数的特性你就知道如何处理数据了。或者可以参考vector的测试,它们两个容器的共性可以说几乎一样。