<C++> STL_deque

10 篇文章 0 订阅
本文详细介绍了C++标准库中的deque数据结构,包括其高效插入删除特性、构造与析构、容量管理、元素访问、修改操作以及迭代器。同时讨论了deque作为STLstack和queue底层容器的原因,强调了其在特定场景下的优势和局限性。
摘要由CSDN通过智能技术生成

1.deque的使用

deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和 删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tp0r6ALo-1693144180150)(C:\Users\32784\AppData\Roaming\Typora\typora-user-images\image-20230827212550686.png)]

构造和析构

  • std::deque<T>:创建一个存储类型为 T 的元素的双端队列。
  • std::deque(const std::deque<T>& other):复制构造函数,用另一个双端队列初始化当前队列。
  • ~std::deque():析构函数,释放内存。

示例:

#include <iostream>
#include <deque>

int main() {
    // 创建一个存储整数的双端队列
    std::deque<int> myDeque;

    // 在队尾插入元素
    myDeque.push_back(10);
    myDeque.push_back(20);
    myDeque.push_back(30);

    // 复制构造一个新的双端队列
    std::deque<int> anotherDeque = myDeque;

    // 输出原始队列的内容
    std::cout << "Original deque content:";
    for (const int& value : myDeque) {
        std::cout << " " << value;
    }
    std::cout << std::endl;

    // 输出复制构造的队列的内容
    std::cout << "Copied deque content:";
    for (const int& value : anotherDeque) {
        std::cout << " " << value;
    }
    std::cout << std::endl;

    // 注意:析构函数会在作用域结束时自动调用,释放内存
    return 0;
}

容量相关

  • size():返回队列中元素的数量。
  • empty():检查队列是否为空。

示例:

#include <deque>
#include <iostream>

int main() {
    std::deque<int> myDeque = {10, 20, 30, 40};

    std::cout << myDeque.size() << std::endl; //4
    std::cout << myDeque.empty() << std::endl;//0

    return 0;
}
  • resize(new_size):调整队列的大小。
void resize(size_type count); 
void resize(size_type count, const value_type& value);
  • count:新的队列大小。
  • value:在增加队列大小时用于填充新元素的默认值。

功能:

  • 如果 count 小于当前队列的大小,resize 会移除尾部的元素,使队列的大小变为 count
  • 如果 count 大于当前队列的大小,resize 会在尾部插入足够数量的默认值为 value 的元素,使队列的大小变为 count

示例:

#include <iostream>
#include <deque>

int main() {
    std::deque<int> myDeque = {10, 20, 30};

    // 调整队列大小为 5,填充默认值 0
    myDeque.resize(5);

    for (const int& value : myDeque) {
        std::cout << " " << value;   // 10 20 30 0 0
    }
    std::cout << std::endl;

    // 调整队列大小为 3
    myDeque.resize(3);

    for (const int& value : myDeque) {
        std::cout << " " << value;  // 10 20 30
    }
    std::cout << std::endl;

    return 0;
}

元素访问

  • at(index):函数返回指定索引处的元素,并在访问之前进行范围检查。如果索引超出了双端队列的有效范围,将引发 std::out_of_range 异常。
  • operator[](index):运算符返回指定索引处的元素,但不进行范围检查。如果索引超出了有效范围,行为将是未定义的。
  • front():返回第一个元素的引用。
  • back():返回最后一个元素的引用。

示例:

#include <deque>
#include <iostream>

int main() {
    // 创建一个存储字符串的双端队列
    std::deque<std::string> myDeque;

    // 在队尾插入元素
    myDeque.push_back("Alice");
    myDeque.push_back("Bob");
    myDeque.push_back("Charlie");

    // 使用 at(index) 进行范围安全的访问
    try {
        std::string value = myDeque.at(1);// 索引1处的元素是 "Bob"
        std::cout << value << std::endl;  //Bob
    } catch (const std::out_of_range &e) {
        std::cout << "Index is out of range." << std::endl;
    }

    // 使用 operator[](index) 进行不安全的访问
    std::string unsafeValue = myDeque[2];// 索引2处的元素是 "Charlie"
    std::cout << "不安全: " << unsafeValue << std::endl;

    // 使用 front() 访问第一个元素
    std::string firstElement = myDeque.front();
    std::cout << firstElement << std::endl;

    // 使用 back() 访问最后一个元素
    std::string lastElement = myDeque.back();   //Alice
    std::cout << lastElement << std::endl;      //Charlie

    return 0;
}

修改操作

  • push_back(value):在队尾插入一个元素。
  • push_front(value):在队首插入一个元素。
  • pop_back():移除队尾的元素。
  • pop_front():移除队首的元素。
  • insert(pos, value):在指定位置插入一个元素。
  • erase(pos):移除指定位置的元素。
  • clear():移除所有元素。

示例:

#include <deque>
#include <iostream>

int main() {
    std::deque<int> myDeque;
    // 在队尾插入元素
    myDeque.push_back(10);
    myDeque.push_back(20);

    // 在队首插入元素
    myDeque.push_front(5);

    // 输出队列内容
    for (const int &value: myDeque) {
        std::cout << value << " ";//5 10 20
    }
    std::cout << std::endl;

    // 移除队尾的元素
    myDeque.pop_back();

    // 移除队首的元素
    myDeque.pop_front();

    // 输出队列内容
    for (const int &value: myDeque) {
        std::cout << value << " ";//10
    }
    std::cout << std::endl;

    // 在指定位置插入元素
    std::deque<int>::iterator it = myDeque.insert(myDeque.begin() + 1, 15);

    // 输出队列内容
    for (const int &value: myDeque) {
        std::cout << value << " ";//10 15
    }
    std::cout << std::endl;

    // 移除指定位置的元素
    myDeque.erase(it);

    // 输出队列内容
    for (const int &value: myDeque) {
        std::cout << value << " ";//10
    }
    std::cout << std::endl;

    // 清空队列
    myDeque.clear();

    if (myDeque.empty()) {
        std::cout << "Deque is empty.";
    } else {
        std::cout << "Deque is not empty.";
    }
    std::cout << std::endl;

    return 0;
}

迭代器

  • begin()end():返回指向第一个元素和尾后元素的迭代器。

示例:

#include <iostream>
#include <deque>

int main() {
    std::deque<int> myDeque = {10, 20, 30, 40};

    for (std::deque<int>::iterator it = myDeque.begin(); it != myDeque.end(); ++it) {
        std::cout << " " << *it;  //10 20 30 40
    }
    std::cout << std::endl;

    return 0;
}
  • rbegin()rend():返回指向最后一个元素和首元素前一个位置的逆向迭代器。

示例:

#include <iostream>
#include <deque>

int main() {
    std::deque<int> myDeque = {10, 20, 30, 40};

    for (std::deque<int>::reverse_iterator rit = myDeque.rbegin(); rit != myDeque.rend(); ++rit) {
        std::cout << " " << *rit;   //40 30 20 10
    }
    std::cout << std::endl;

    return 0;
}

2.deque的原理介绍

deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示:

在这里插入图片描述

双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:

在这里插入图片描述

那deque是如何借助其迭代器维护其假想连续的结构呢?

在这里插入图片描述

3.deque的优缺点

与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是比vector高的。 与list比较,其底层是连续空间空间利用率比较高,不需要存储额外字段。 但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构 时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。

4.为什么选择deque作为stack和queue的底层默认容器

stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性结构,都可以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:

  1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。
  2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。

结合了deque的优点,而完美的避开了其缺陷。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 这是一个运行错误,发生在第171行第16列,错误信息是“引用绑定到未对齐的地址0xbebebebebebec0ba,类型为'int',需要4字节对齐(stl_deque.h)”,指向的地址是0xbebebebebebec0ba,但是该地址没有被打印出来。总结是未定义行为。该错误信息出现在stl_deque.h文件的第180行第16列。 ### 回答2: 这个错误是由于使用了一个未对齐的内存地址引用所导致的。该错误指向了一个STL deque库中的模板函数stl_deque.h。错位参考绑定引用到不对齐的地址上,这个地址是0xbebebebebebec0ba,该地址不满足int类型需要的4字节对齐。同时,该错误指出该地址的指针指向这里,但是由于未知原因无法打印出该地址,即该地址的内存无法被读取。 这一错误可以由许多原因引起,例如使用不正确的指针值、内存泄漏、堆栈溢出或内存覆盖。其中,最常见的原因是访问未初始化或已经释放的堆内存。这个错误会导致程序崩溃或不可预测的行为,因此必须加以调试和修复。 对于这种错误,建议使用内存分析工具来确定具体的原因。例如,使用Valgrind和GDB可以检测出内存泄漏和许多其他内存错误。同时,也可以通过仔细地检查代码逻辑,确保指针和地址的处理是正确的来避免出现此类错误。另外,可以使用C++11标准中的std::aligned_storage和std::align函数来进行内存对齐,以避免该类错误的出现。 ### 回答3: 这是一个运行时错误,意味着在程序运行时出现了问题。具体来说,这个错误是在调用STL库中的deque容器时发生的。错误的信息是“reference binding to misaligned address”,也就是说,引用绑定到了一个没有按照4个字节对齐的地址上。 出现这个错误的原因可能是内存泄漏、访问越界或者数据类型不匹配等问题。这里的0xbebebebebebec0ba是一个指针地址,指向一个在内存中的位置,但是这个地址可能因为某些原因导致了访问越界或者数据类型不匹配的问题。 要解决这个问题,首先需要找到引起这个错误的原因。可以使用一些调试工具,比如Valgrind或者GDB等,对程序进行调试,找到出现错误的位置。然后根据错误的信息进行分析,进一步确定问题所在。最后,针对问题进行修复和改进,使代码更加健壮、可靠。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值