展开菜单
首页 精品内容 本月促销 装机必备 Windows PDF教程 macOS软件 IOS软件 Android AI 专题
全部分类

当前位置:

首页 > 编程开发 > C++ STL实现队列与栈方法

C++ STL实现队列与栈方法

C++中使用std::stack和std::queue适配器可高效实现栈和队列,二者默认以std::deque为底层容器,提供语义清晰、类型安全的接口,并支持替换底层容器以优化性能;在多线程环境下需通过互斥锁等机制确保线程安全。

本文目录

    C++中使用std::stack和std::queue适配器可高效实现栈和队列,二者默认以std::deque为底层容器,提供语义清晰、类型安全的接口,并支持替换底层容器以优化性能;在多线程环境下需通过互斥锁等机制确保线程安全。

    C++如何使用STL容器实现队列和栈

    在C++中,要实现队列(Queue)和栈(Stack)这两种基本的数据结构,最简洁、最符合现代C++实践的方式就是利用标准模板库(STL)中提供的适配器容器std::queue和std::stack。它们并不是全新的数据结构,而是将现有的序列容器(如std::deque、std::list或std::vector)封装起来,只暴露队列或栈特有的操作接口,从而保证了操作的语义正确性。

    解决方案

    使用std::queue和std::stack非常直观,它们默认都以std::deque(双端队列)作为底层容器,因为std::deque在两端插入和删除元素都非常高效。

    实现栈 (Stack)

    栈是一种“后进先出”(LIFO, Last In, First Out)的数据结构。std::stack提供了push(入栈)、pop(出栈)、top(查看栈顶元素)、empty(判断是否为空)和size(获取元素数量)等操作。

    #include 
    #include  // 包含stack头文件
    #include 
    
    void demonstrateStack() {
        std::stack myStack;
    
        // 入栈操作
        myStack.push("Apple");
        myStack.push("Banana");
        myStack.push("Cherry");
        std::cout << "当前栈顶元素: " << myStack.top() << std::endl; // Cherry
    
        // 栈的大小
        std::cout << "栈中元素数量: " << myStack.size() << std::endl; // 3
    
        // 遍历并出栈
        std::cout << "开始出栈: " << std::endl;
        while (!myStack.empty()) {
            std::cout << "弹出: " << myStack.top() << std::endl;
            myStack.pop();
        }
        std::cout << "栈是否为空? " << (myStack.empty() ? "是" : "否") << std::endl; // 是
    }

    实现队列 (Queue)

    队列是一种“先进先出”(FIFO, First In, First Out)的数据结构。std::queue提供了push(入队)、pop(出队)、front(查看队头元素)、back(查看队尾元素)、empty(判断是否为空)和size(获取元素数量)等操作。

    #include 
    #include  // 包含queue头文件
    #include 
    
    void demonstrateQueue() {
        std::queue myQueue;
    
        // 入队操作
        myQueue.push(10);
        myQueue.push(20);
        myQueue.push(30);
        std::cout << "当前队头元素: " << myQueue.front() << std::endl; // 10
        std::cout << "当前队尾元素: " << myQueue.back() << std::endl;  // 30
    
        // 队列的大小
        std::cout << "队列中元素数量: " << myQueue.size() << std::endl; // 3
    
        // 遍历并出队
        std::cout << "开始出队: " << std::endl;
        while (!myQueue.empty()) {
            std::cout << "出队: " << myQueue.front() << std::endl;
            myQueue.pop();
        }
        std::cout << "队列是否为空? " << (myQueue.empty() ? "是" : "否") << std::endl; // 是
    }

    将这两个示例代码放在main函数中调用,就能清晰地看到它们的工作方式。

    为什么STL选择提供适配器容器,而不是直接使用序列容器?

    这是一个很棒的问题,它触及了软件设计中的一个核心原则:接口与实现的分离。STL之所以提供std::stack和std::queue作为容器适配器,而不是直接让开发者去用std::vector或std::list模拟,主要有几个深层考量:

    首先,语义清晰性。当你看到代码中使用了std::stack,你立刻就知道这个数据结构遵循LIFO原则,它的操作(push、pop、top)也严格限定在栈的语义范围内。如果直接用std::vector,虽然可以通过push_back和pop_back实现栈,但std::vector还提供了operator[]、insert、erase等大量不符合栈语义的操作。这会模糊代码意图,也可能导致误用,比如不小心从中间插入或删除元素,从而破坏了栈的结构。适配器强制了接口,确保了数据结构的行为符合其定义。

    其次,类型安全和约束。适配器容器通过封装底层容器,只暴露了特定数据结构所需的操作。这是一种“最少知识原则”的应用。它降低了出错的可能性,因为你无法执行不属于栈或队列的操作。这在大型项目或团队协作中尤为重要,它为开发者提供了一个明确且受限的接口,减少了心智负担和潜在的bug。

    再者,底层实现的可替换性。std::stack和std::queue允许你指定不同的底层序列容器(如std::deque、std::list、甚至std::vector——尽管std::vector作为std::queue的底层容器效率不高)。这意味着,如果未来你发现默认的std::deque在特定场景下性能不佳,你可以不改变上层逻辑(即栈或队列的操作语义),仅仅通过修改模板参数就能切换到底层容器,比如换成std::list。这种灵活性是直接使用单一序列容器无法比拟的。它将“我需要一个栈”和“这个栈应该如何高效地实现”这两个问题解耦了。

    所以,与其说STL“没有直接使用”,不如说它提供了一个更高层次的抽象,让开发者能够以更清晰、更安全、更灵活的方式使用这些经典数据结构。这是一种设计上的优雅,它让我们的代码不仅能工作,而且能更好地表达意图。

    自定义底层容器:如何选择最适合你的场景?

    虽然std::stack和std::queue默认使用std::deque作为底层容器,但在某些特定场景下,我们可能需要根据性能特征来选择std::list或std::vector。理解它们的优缺点对于做出明智的选择至关重要。

    1. std::deque (默认选择)

    • 优点: std::deque(双端队列)在两端(头部和尾部)插入和删除元素都具有常数时间复杂度O(1)。这是因为std::deque在内部通常实现为一系列固定大小的块,这些块的指针存储在一个映射表中,使得它能高效地扩展。这对于栈(只在一端操作)和队列(两端操作)都是理想的。
    • 缺点: 相比std::vector,std::deque的元素不保证存储在连续的内存空间中,这可能导致缓存局部性稍差,在某些对缓存敏感的场景下性能略逊。随机访问(通过operator[])虽然也是O(1),但通常比std::vector慢一点。
    • 适用场景: 绝大多数情况下,std::deque都是std::stack和std::queue的最佳默认选择。如果你不确定,就用它。

    2. std::list

    • 优点: std::list(双向链表)在任何位置插入和删除元素都具有常数时间复杂度O(1),因为它只需要修改前后节点的指针。对于栈和队列,这意味着push和pop操作效率极高,且不会导致内存重新分配和元素拷贝(这是std::vector可能面临的问题)。
    • 缺点: std::list不支持随机访问(例如operator[]),访问元素需要O(n)的时间复杂度。每个元素都需要额外的内存来存储前后节点的指针,导致内存开销较大。缓存局部性极差,因为元素在内存中不连续。
    • 适用场景:
      • 栈: 如果你的栈需要存储大量元素,并且你非常关心每次push/pop操作的稳定常数时间性能,且内存重新分配的开销是不可接受的,std::list可能是一个选择。
      • 队列: std::list是实现队列的优秀选择,因为它在头部和尾部插入/删除都非常高效。如果你的队列元素数量变化频繁,且对内存分配和性能的稳定性有较高要求,可以考虑std::list。
      • 避免: 如果你需要频繁地查看队列或栈的中间元素,或者对内存使用有严格限制,std::list不是一个好选择。

    3. std::vector

    • 优点: std::vector(动态数组)的元素存储在连续的内存空间中,具有极佳的缓存局部性,随机访问效率最高(O(1))。
    • 缺点:
      • 作为栈的底层: std::vector作为栈的底层容器是高效的,因为push_back和pop_back(对应栈的push和pop)通常是O(1)摊销时间。但当容量不足时,push_back会导致内存重新分配和所有元素的拷贝,这会带来O(n)的开销。
      • 作为队列的底层: std::vector作为队列的底层容器效率很低。push操作(push_back)是高效的,但pop操作(从头部删除元素)需要移动所有后续元素,其时间复杂度为O(n)。这使得std::vector不适合作为std::queue的底层容器。
    • 适用场景:
      • 栈: 如果你对栈的内存连续性有要求(例如,为了更好的缓存性能),并且可以接受偶尔的O(n)重新分配开销,或者你知道栈的最大容量并能预先reserve,那么std::vector可以作为std::stack的底层容器。
      • 避免: 绝对不要将std::vector作为std::queue的底层容器,除非你的队列几乎从不执行pop操作。

    如何指定底层容器:

    #include 
    #include 
    #include  // 用于作为底层容器
    #include    // 用于作为底层容器
    #include   // 默认,但也可以显式指定
    
    // 使用std::list作为底层容器的栈
    std::stack> myStackWithList;
    
    // 使用std::vector作为底层容器的栈
    // 注意:std::vector作为栈的底层,其push_back和pop_back操作是高效的
    std::stack> myStackWithVector;
    
    // 使用std::list作为底层容器的队列
    std::queue> myQueueWithList;
    
    // 使用std::deque作为底层容器的队列(显式指定,与默认行为一致)
    std::queue> myQueueWithDeque;

    选择合适的底层容器,需要根据你的具体需求(性能、内存、操作模式)进行权衡。大多数时候,std::deque的平衡性使其成为默认的最佳选择,但了解其他选项能帮助你在特定瓶颈出现时进行优化。

    在多线程环境中,STL队列和栈的线程安全问题及初步考量

    当我们将STL的std::queue和std::stack引入多线程编程时,一个非常关键且必须严肃对待的问题就是线程安全性。简单来说,std::queue和std::stack本身并不是线程安全的。这意味着,如果多个线程同时对同一个队列或栈进行push、pop、top/front/back或size等操作,就可能发生数据竞争(data race),导致未定义行为。这包括但不限于数据损坏、程序崩溃、或者得到错误的结果。

    为什么它们不是线程安全的?

    STL容器的设计哲学是“提供高效且通用的数据结构,将线程安全留给用户去实现”。容器内部的操作,例如在push时修改内部指针、分配内存,或者在pop时更新状态,这些都不是原子操作。多个线程同时执行这些非原子操作时,它们的执行顺序是不可预测的,可能导致内部状态处于不一致的中间状态。

    举个例子,一个线程正在push一个元素,它可能已经分配了内存并准备将数据写入,但还没来得及更新容器的大小或尾部指针。此时,另一个线程尝试pop,它可能读取到一个过时的大小,或者尝试访问一个尚未完全写入的元素,甚至访问到无效内存。

    如何实现线程安全?

    要让std::queue或std::stack在多线程环境下安全地工作,我们通常需要引入同步机制。最常见的方法是使用互斥锁(std::mutex)。

    一个基本的线程安全队列或栈通常会是这样的:

    #include 
    #include 
    #include  // 对于阻塞队列/栈很有用
    #include           // 用于异常处理
    
    // 示例:一个简单的线程安全队列
    template 
    class ThreadSafeQueue {
    private:
        std::queue q;
        mutable std::mutex mtx; // mutable allows const methods to lock
        std::condition_variable cv; // 用于等待和通知
    
    public:
        void push(const T& value) {
            std::lock_guard lock(mtx); // 自动加锁解锁
            q.push(value);
            cv.notify_one(); // 通知一个等待的线程有新数据了
        }
    
        T pop() {
            std::unique_lock lock(mtx); // 允许在等待时解锁
            // 等待直到队列不为空
            cv.wait(lock, [this]{ return !q.empty(); });
            T value = q.front();
            q.pop();
            return value;
        }
    
        bool try_pop(T& value) { // 非阻塞式pop
            std::lock_guard lock(mtx);
            if (q.empty()) {
                return false;
            }
            value = q.front();
            q.pop();
            return true;
        }
    
        bool empty() const {
            std::lock_guard lock(mtx);
            return q.empty();
        }
    
        size_t size() const {
            std::lock_guard lock(mtx);
            return q.size();
        }
    };
    
    // 线程安全栈的实现类似,只是操作接口不同

    初步考量和注意事项:

    1. 互斥锁的粒度: 上述示例中,每个操作都加锁,这是最直接的实现方式。但如果操作非常频繁且锁竞争激烈,这可能成为性能瓶颈。在某些高级场景中,可能会考虑更细粒度的锁,或者无锁(lock-free)数据结构,但这会显著增加实现的复杂性。
    2. 阻塞与非阻塞: pop操作可能需要等待队列中有元素。上面的pop方法是阻塞的(使用std::condition_variable),即如果没有元素,调用线程会等待直到有元素可用。你也可以提供一个非阻塞的try_pop方法,如果队列为空则立即返回一个指示(例如false)。
    3. 异常安全: 确保在操作过程中抛出异常时,锁能够正确释放。std::lock_guard和std::unique_lock是RAII(Resource Acquisition Is Initialization)的典范,它们能够自动管理锁的生命周期,非常适合处理异常。
    4. top()/front()的陷阱: 如果一个线程调用top()或front()获取了栈顶/队头元素,但在它使用这个元素之前,另一个线程执行了pop操作,那么之前获取的元素可能已经失效,或者它所指向的内存已被释放或重用。因此,通常建议将pop和获取元素合并为一个操作,或者确保在获取元素后立即使用。我的pop示例就是返回了值,避免了这个问题。
    5. 死锁风险: 如果你的线程安全容器与其他锁或资源交互,需要警惕死锁的风险。始终保持一致的加锁顺序,或者使用std::lock配合std::adopt_lock等高级技术。

    在实际项目中,尤其是在高性能或高并发场景,直接手写一个完美无缺的线程安全容器是很有挑战的。通常,我们会倾向于使用成熟的并发库,例如Intel TBB (Threading Building Blocks) 中的concurrent_queue,或者Boost库中的并发容器,它们提供了经过充分测试和优化的线程安全数据结构。但理解其底层原理和如何使用std::mutex、std::condition_variable来构建,是掌握现代C++并发编程的基础。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    精品专题 更多
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    IOS软件

    正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

    AI

    正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

    Mac软件 更多
    Postman macOS版
    Postman macOS版

    Postman是一套面向API全生命周期的开发与测试平台,可在Mac上发送和调试多种协议请求,管理集合、环境及认证信息,并完成脚本测试、模拟服务、监控、文档发布和团队协作。它适合开发、测试及运维人员使用,提供个人免费方案。

    Docker Desktop macOS版
    Docker Desktop macOS版

    Docker Desktop 是 Docker 官方为 Mac 提供的容器开发环境,集成 Docker Engine、命令行工具、Compose、Build、Kubernetes 与图形化管理界面。

    Android Studio macOS版
    Android Studio macOS版

    Android Studio 是 Google 面向安卓应用开发提供的官方集成开发环境,基于 IntelliJ IDEA 构建。它整合智能代码编辑、Gradle 构建、界面预览、设备模拟、性能分析、调试测试及 APK 分析等工具。

    Xcode macOS版
    Xcode macOS版

    Xcode 是苹果面向 Mac 提供的官方集成开发环境,将代码编辑、项目构建、SwiftUI 实时预览、设备模拟、自动化测试、断点调试、性能分析和应用分发整合在同一套工作流中,适合开发与维护 iPhone、iPad、Mac、Apple Wa

    JetBrains WebStorm macOS版
    JetBrains WebStorm macOS版

    JetBrains WebStorm 是面向 JavaScript、TypeScript 和现代 Web 技术的专业集成开发环境,可智能理解项目结构,提供代码补全、重构、错误检查、运行调试、测试及 Git 工具。

    PhpStorm macOS版
    PhpStorm macOS版

    PhpStorm 是 JetBrains 推出的专业 PHP 集成开发环境,可在 Mac 上完成代码编写、智能检查、重构、调试、测试和数据库管理。它支持主流 PHP 框架、Composer、Git、Docker 与远程解释器。

    JetBrains PyCharm macOS版
    JetBrains PyCharm macOS版

    JetBrains PyCharm 是面向 Python 开发者的集成开发环境,提供智能补全、代码检查、重构、调试、测试、版本控制及虚拟环境管理功能。Mac 版兼容 Intel 与 Apple 芯片,核心功能可免费使用。

    JetBrains IntelliJ IDEA macOS版
    JetBrains IntelliJ IDEA macOS版

    IntelliJ IDEA 是 JetBrains 推出的专业集成开发环境,重点服务于 Java、Kotlin 与 JVM 项目开发。软件集成代码补全、静态检查、重构、调试、测试、版本控制及构建工具,并可借助插件扩展语言和框架支持。

    Visual Studio Code macOS版
    Visual Studio Code macOS版

    Visual Studio Code 是微软提供的免费代码编辑器,支持 macOS,并为 Intel 与 Apple 芯片 Mac 提供适配版本。软件集代码编辑、调试、Git 源代码管理、集成终端和扩展市场于一体。

    REAPER macOS版
    REAPER macOS版

    REAPER是Cockos开发的数字音频工作站,提供多轨音频与MIDI录制、剪辑、处理、混音和母带制作工具。Mac版兼容Intel与Apple芯片,支持AU、VST、VST3、CLAP等插件格式,并提供高度可定制的工作流程。

    Adobe Audition macOS版
    Adobe Audition macOS版

    Adobe Audition 是面向播客、影视后期、音乐制作及内容创作者的专业音频工作站,提供波形精修、多轨录制与混音、频谱编辑、降噪修复、响度匹配和效果处理工具,并可与 Adobe Premiere 协同完成视频声音制作。

    Pro Tools macOS版
    Pro Tools macOS版

    Pro Tools 是 Avid 推出的专业数字音频工作站,覆盖多轨录音、无损剪辑、MIDI 编曲、插件处理、自动化混音及声音与画面同步等工作。它拥有 Intro、Artist、Studio 和 Ultimate 等方案。

    WINDOWS 更多
    PixPix 网页版
    PixPix 网页版

    PixPix 是面向品牌、卖家和内容团队的在线 AI 图片视频创作平台,提供图视频 Agent、无限画布、图片生成、视频生成、自然语言编辑及商品套图、产品精修和带货视频等电商工具。

    Postman Windows版
    Postman Windows版

    Postman 是面向 API 全生命周期的开发与协作平台。Windows 桌面版可用于构建和发送 HTTP、GraphQL、gRPC、WebSocket、MQTT 等请求,并提供环境变量、集合、脚本测试、模拟服务、监控、文档及团队工作区等

    Docker Desktop Windows版
    Docker Desktop Windows版

    Docker Desktop 为 Windows 开发者提供集成式容器环境,内置 Docker Engine、命令行工具、Compose、Build 与 Kubernetes 等组件,可在本机完成镜像构建、容器运行、日志查看和资源管理。

    Android Studio Windows版
    Android Studio Windows版

    Android Studio 是 Google 面向 Android 应用开发提供的官方集成开发环境,以 IntelliJ IDEA 为基础,集成智能代码编辑、Gradle 构建、设备模拟、界面预览、调试测试及性能分析工具,

    JetBrains WebStorm Windows版
    JetBrains WebStorm Windows版

    WebStorm 是 JetBrains 面向 JavaScript、TypeScript 和现代 Web 技术打造的集成开发环境。它提供项目级代码分析、智能补全、安全重构、导航搜索,并集成调试、测试、终端、Git 与常用前端工具,

    JetBrains PhpStorm Windows版
    JetBrains PhpStorm Windows版

    PhpStorm 是 JetBrains 面向 PHP 与现代 Web 开发打造的专业集成开发环境,提供代码补全、静态检查、重构、调试、测试、数据库、版本控制及前端语言支持。Windows 用户可直接下载安装程序,

    JetBrains PyCharm Windows版
    JetBrains PyCharm Windows版

    PyCharm 是 JetBrains 推出的 Python 集成开发环境,提供智能代码补全、错误检查、重构、调试、测试、Git、终端及 Jupyter Notebook 支持。统一版安装后可体验 30 天 Pro 功能,

    JetBrains IntelliJ IDEA Windows版
    JetBrains IntelliJ IDEA Windows版

    JetBrains IntelliJ IDEA 是以 Java、Kotlin 和 JVM 项目为核心的专业集成开发环境,提供智能补全、代码检查、安全重构、调试、测试、版本控制及构建工具集成。统一发行版的核心功能可免费用于商业和非商业项目,

    Visual Studio Code Windows版
    Visual Studio Code Windows版

    Visual Studio Code 是微软推出的免费代码编辑器,Windows 版集成智能补全、代码导航、调试、终端和版本控制,并可通过扩展添加语言、格式化及远程开发能力。它启动较快、配置灵活,

    REAPER Windows版
    REAPER Windows版

    REAPER 是 Cockos 推出的数字音频工作站,适用于录音、MIDI 编曲、音频编辑、混音、母带制作及声音设计。软件安装包小巧,支持多轨录制、灵活路由、自动化、第三方插件和脚本扩展,并提供完整功能的 60 天免费评估期。

    Adobe Audition Windows版
    Adobe Audition Windows版

    Adobe Audition 是面向播客、影视后期、广播与音乐制作的专业音频工作站,提供波形编辑、多轨混音、频谱分析、录音、降噪修复和效果处理等功能。Windows 版通过 Adobe Creative Cloud 安装和管理,

    Pro Tools Windows版
    Pro Tools Windows版

    Pro Tools 是 Avid 推出的专业数字音频工作站,提供多轨录音、音频剪辑、MIDI编曲、插件处理、自动化混音及影视声音制作能力。它适合音乐人、录音师、制作人和音频后期团队使用,并提供免费入门版、订阅方案及永久授权选择。