首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

C++ STL源码剖析 2-std::deque 双端队列

  • 25-02-19 07:41
  • 3343
  • 5243
blog.csdn.net

系列文章目录

点击直达——文章总目录


文章目录

  • 系列文章目录
  • C++ STL源码剖析 2-std::deque 双端队列
    • Overview
    • 1.std::deque 定义
      • 1.1.关键点
    • 2.std::deque 解析
      • 2.1.特点
      • 2.2.常用操作
      • 2.3.示例代码
      • 2.4.输出结果
      • 2.5.注意事项
    • 3.std::deque 源码剖析
    • 4.如何使用 C++ 标准库中的容器进行高效的数据结构设计?
      • 4.1. `std::vector`
      • 4.2. `std::deque`
      • 4.3. `std::list`
      • 4.4. `std::forward_list`
      • 4.5. `std::set` 和 `std::map`
      • 4.6. `std::unordered_set` 和 `std::unordered_map`
      • 4.7. `std::array`
      • 4.8. `std::tuple`
      • 4.9. `std::optional`
      • 4.10. `std::variant`
      • 4.11其他注意事项
    • 5.如何优化 C++ 容器的内存管理以减少内存泄漏的风险?
    • 关于作者


C++ STL源码剖析 2-std::deque 双端队列

Overview

  • C++不练习coding,就相当于空中楼阁,基础不扎实
  • 光吃不干,等于没吃
  • 双端队列,它支持在序列的前端和后端快速插入和删除元素

1.std::deque 定义

std::deque(double-ended queue,双端队列)是C++标准模板库(STL)中的一个序列容器,定义在头文件中。它支持在序列的前端和后端快速插入和删除元素。

std::deque的定义如下:

#include 

template <class T, class Allocator = std::allocator<T> >
class deque {
public:
    // 类型定义
    typedef T value_type;
    typedef Allocator allocator_type;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    typedef T& reference;
    typedef const T& const_reference;
    typedef T* pointer;
    typedef const T* const_pointer;
    typedef std::allocator_traits<Allocator> allocator_traits_type;
    
    // 迭代器类型
    typedef implementation_defined iterator;
    typedef implementation_defined const_iterator;
    typedef std::reverse_iterator<iterator> reverse_iterator;
    typedef std::reverse_iterator<const_iterator> const_reverse_iterator;

    // 构造函数和析构函数
    explicit deque(const Allocator& = Allocator());
    explicit deque(size_type n, const Allocator& = Allocator());
    deque(size_type n, const value_type& value, const Allocator& = Allocator());
    template <class InputIterator>
    deque(InputIterator first, InputIterator last, const Allocator& = Allocator());
    deque(const deque& other);
    deque(deque&& other);
    deque(std::initializer_list<value_type> init, const Allocator& = Allocator());
    ~deque();

    // 赋值运算符
    deque& operator=(const deque& other);
    deque& operator=(deque&& other);
    deque& operator=(std::initializer_list<value_type> init);

    // 迭代器
    iterator begin() noexcept;
    const_iterator begin() const noexcept;
    iterator end() noexcept;
    const_iterator end() const noexcept;
    reverse_iterator rbegin() noexcept;
    const_reverse_iterator rbegin() const noexcept;
    reverse_iterator rend() noexcept;
    const_reverse_iterator rend() const noexcept;
    const_iterator cbegin() const noexcept;
    const_iterator cend() const noexcept;
    const_reverse_iterator crbegin() const noexcept;
    const_reverse_iterator crend() const noexcept;

    // 容量
    size_type size() const noexcept;
    size_type max_size() const noexcept;
    void resize(size_type new_size);
    void resize(size_type new_size, const value_type& x);

    // 修改器
    void clear() noexcept;
    void push_back(const value_type& value);
    void push_back(T&& value);
    template <class... Args>
    void emplace_back(Args&&... args);
    void push_front(const value_type& value);
    void push_front(T&& value);
    template <class... Args>
    void emplace_front(Args&&... args);
    void pop_back();
    void pop_front();

    // 访问器
    reference operator[](size_type n);
    const_reference operator[](size_type n) const;
    reference at(size_type n);
    const_reference at(size_type n) const;
    reference front();
    const_reference front() const;
    reference back();
    const_reference back() const;

    // 插入器和移除器
    iterator insert(iterator pos, const value_type& value);
    iterator insert(iterator pos, T&& value);
    template <class... Args>
    iterator emplace(iterator pos, Args&&... args);
    void insert(iterator pos, size_type n, const value_type& value);
    template <class InputIterator>
    void insert(iterator pos, InputIterator first, InputIterator last);
    void insert(iterator pos, std::initializer_list<value_type> ilist);
    iterator erase(iterator pos);
    iterator erase(iterator first, iterator last);

    // 特殊操作
    void swap(deque& other) noexcept(/* see below */);
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96

1.1.关键点

  1. 模板参数:

    • T:存储在deque中的元素类型。
    • Allocator:分配器类型,默认为std::allocator。
  2. 类型定义:

    • value_type:元素类型。
    • allocator_type:分配器类型。
    • size_type:无符号整数类型,用于表示大小。
    • difference_type:带符号整数类型,用于表示索引差。
    • reference:元素的引用类型。
    • const_reference:常量元素的引用类型。
    • pointer和const_pointer:元素的指针类型。
  3. 迭代器:

    • iterator和const_iterator:分别表示可变和不可变的迭代器。
    • reverse_iterator和const_reverse_iterator:分别表示可变和不可变的反向迭代器。
  4. 构造函数:

    • 提供多种构造函数,包括默认构造、指定大小构造、范围构造等。
  5. 赋值运算符:

    • 支持拷贝赋值、移动赋值和初始化列表赋值。
  6. 迭代器操作:

    • 提供begin()和end()函数来获取迭代器。
  7. 容量操作:

    • 提供size()和max_size()函数来获取容器的大小。
  8. 修改器:

    • 提供push_back()、push_front()、pop_back()和pop_front()函数来在两端添加和删除元素。
  9. 访问器:

    • 提供operator[]和at()函数来访问元素。
  10. 插入器和移除器:

    • 提供insert()和erase()函数来插入和删除元素。
  11. 特殊操作:

    • 提供swap()函数来交换两个deque的内容。

std::deque的内部实现通常使用多个固定大小的数组块来存储元素,这些块在需要时动态分配和释放。这种设计使得std::deque在两端的插入和删除操作非常高效。

2.std::deque 解析

在C++标准模板库(STL)中,std::deque(双端队列)是一个容器,它支持高效的元素插入和移除操作,无论是在序列的前端还是后端。std::deque的全称是“double-ended queue”,即双端队列。

2.1.特点

  1. 高效的插入和删除:可以在序列的前端和后端快速插入和删除元素。
  2. 内存分配:std::deque通常使用多个固定大小的数组块来存储元素,这些块在需要时动态分配和释放。
  3. 迭代器:提供随机访问迭代器。
  4. 大小:动态的,可以在运行时改变。
  5. 异常安全:大部分操作都是异常安全的。

2.2.常用操作

  • push_back(): 在序列的末尾添加一个元素。
  • pop_back(): 移除序列末尾的元素。
  • push_front(): 在序列的开头添加一个元素。
  • pop_front(): 移除序列开头的元素。
  • erase(): 删除指定位置的元素或一系列元素。
  • insert(): 在指定位置插入一个或多个元素。
  • clear(): 清空整个容器。
  • size(): 返回容器中元素的数量。

2.3.示例代码

#include 
#include 

int main() {
    std::deque<int> d;

    // 在deque的末尾添加元素
    d.push_back(10);
    d.push_back(20);
    d.push_back(30);

    // 在deque的开头添加元素
    d.push_front(5);
    d.push_front(3);

    // 打印deque中的元素
    std::cout << "Deque contains: ";
    for (int num : d) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // 删除deque开头的元素
    d.pop_front();

    // 再次打印deque中的元素
    std::cout << "Deque contains after pop_front: ";
    for (int num : d) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

2.4.输出结果

Deque contains: 3 5 10 20 30 
Deque contains after pop_front: 5 10 20 30 
  • 1
  • 2

2.5.注意事项

  • std::deque的随机访问性能可能不如std::vector,因为std::deque可能由多个数组块组成,随机访问可能需要更多的指针跳转。
  • std::deque在序列中间插入和删除元素的性能通常不如在两端操作,因为中间操作可能需要移动多个元素。
  • std::deque的迭代器在插入和删除操作后可能会失效,因此需要谨慎处理迭代器。

std::deque适用于需要在序列的两端频繁插入和删除元素的场景,例如实现队列或双端队列的数据结构。

3.std::deque 源码剖析

std::deque(双端队列)是C++标准模板库(STL)中的一个序列容器,它提供了在两端快速插入和删除元素的能力。std::deque的实现通常使用一个或多个动态分配的数组块,这些块可以提高插入和删除操作的效率,尤其是在序列的前端和后端。

以下是std::deque的一些关键实现细节:

  1. 内存分配:std::deque内部使用一个数组的数组(称为"map"),每个数组块存储固定数量的元素。当需要更多空间时,会分配新的块,并将旧块的元素复制到新块中。这种设计允许在两端快速插入和删除元素,而不需要像std::vector那样可能需要复制整个序列。

  2. 迭代器:std::deque提供了随机访问迭代器,这意味着你可以像使用std::vector一样,通过索引来访问std::deque中的元素。

  3. 构造函数:std::deque有多个构造函数,包括默认构造、复制构造、移动构造以及使用初始化列表的构造等。

  4. 赋值操作:std::deque支持拷贝赋值和移动赋值。

  5. 插入和删除操作:std::deque提供了push_back、push_front、pop_back、pop_front、insert和erase等成员函数来修改容器。

  6. 容量和大小:std::deque提供了size、max_size和resize等成员函数来处理容器的容量和大小。

  7. 特殊操作:std::deque还提供了swap和clear等成员函数来交换和清除容器内容。

  8. 迭代器失效:在std::deque中,插入和删除操作可能会导致迭代器失效,尤其是在序列中间进行操作时。

  9. 性能:std::deque在两端的插入和删除操作是高效的,但随机访问性能通常不如std::vector。

  10. 内存管理:std::deque使用分配器(allocator)来管理内存,这允许使用自定义的内存分配策略。

std::deque的实现细节可能会根据不同的编译器和其版本有所不同,但上述提到的是一些普遍的特性和行为。如果你需要查看特定编译器的实现,可以查看编译器的源代码,例如GCC的libstdc++或Clang的libc++。

在实际使用中,std::deque非常适合需要在序列两端进行快速插入和删除操作的场景,例如实现队列或双端队列的数据结构。

4.如何使用 C++ 标准库中的容器进行高效的数据结构设计?

使用C++标准库中的容器进行高效的数据结构设计,需要根据具体问题选择合适的容器类型,并了解每种容器的特性和适用场景。以下是一些常见容器及其高效使用的方法:

4.1. std::vector

  • 用途:动态数组,适用于需要随机访问元素的场景。
  • 高效使用:
    • 尽量避免频繁的插入和删除操作,特别是在容器的开始部分。
    • 利用reserve()预先分配足够的容量,以减少重新分配内存的次数。
    • 使用emplace_back()直接在容器中构造对象,避免额外的拷贝或移动操作。

4.2. std::deque

  • 用途:双端队列,适用于需要在两端进行插入和删除操作的场景。
  • 高效使用:
    • 利用push_back()和push_front()在两端快速插入元素。
    • 注意,std::deque的中间插入和删除操作可能较慢,因为需要移动元素。

4.3. std::list

  • 用途:双向链表,适用于需要在序列中间频繁插入和删除的场景。
  • 高效使用:
    • 利用splice()高效地合并两个列表或移动元素。
    • 由于没有随机访问能力,避免使用索引访问元素。

4.4. std::forward_list

  • 用途:单向链表,适用于内存使用非常关键的场景。
  • 高效使用:
    • 与std::list类似,但因为是单向链表,所以操作稍微简单一些。
    • 适用于只需要在链表头部或尾部操作的情况。

4.5. std::set 和 std::map

  • 用途:基于红黑树实现的关联容器,适用于需要保持元素有序或快速查找的场景。
  • 高效使用:
    • 利用lower_bound()和upper_bound()进行范围查找。
    • 使用insert()或emplace()直接在容器中构造对象。

4.6. std::unordered_set 和 std::unordered_map

  • 用途:基于哈希表实现的关联容器,适用于需要快速插入和查找的场景。
  • 高效使用:
    • 确保一个好的哈希函数以减少冲突。
    • 适当调整装载因子以平衡内存使用和查找性能。

4.7. std::array

  • 用途:固定大小的数组,适用于数组大小在编译时已知的场景。
  • 高效使用:
    • 利用固定大小的特性进行性能优化。
    • 使用std::array的成员函数进行元素访问。

4.8. std::tuple

  • 用途:存储固定数量的不同类型数据。
  • 高效使用:
    • 使用std::get()访问元素,其中N是元素的索引。
    • 结合std::apply()使用,对元组中的元素应用函数。

4.9. std::optional

  • 用途:用于表示可选值,即可能存在也可能不存在的值。
  • 高效使用:
    • 避免使用指针来表示可选值,减少空指针检查。
    • 使用std::make_optional()来创建对象。

4.10. std::variant

  • 用途:用于在多种类型中选择一种。
  • 高效使用:
    • 避免使用联合体和enum来表示类型标签。
    • 使用std::visit()来访问存储的值。

4.11其他注意事项

  • 迭代器失效:了解容器操作如何影响迭代器的有效性。
  • 异常安全:确保容器操作不会在抛出异常时导致资源泄露。
  • 内存管理:考虑使用自定义分配器来优化内存使用。

通过了解每种容器的特性和适用场景,并结合上述高效使用的方法,你可以设计出既高效又符合需求的数据结构。

5.如何优化 C++ 容器的内存管理以减少内存泄漏的风险?

要优化C++容器的内存管理以减少内存泄漏的风险,可以采取以下措施:

  1. 使用智能指针:利用std::unique_ptr、std::shared_ptr和std::weak_ptr自动管理内存,它们会在适当的时候自动释放内存,从而减少内存泄漏的风险。

  2. 遵循RAII原则:确保资源在对象构造时获取,在对象析构时释放。这样即使发生异常,资源也能得到正确释放,从而避免内存泄漏。

  3. 容器管理内存:使用STL容器(如std::vector、std::list、std::map等)来自动管理内存,避免手动分配和释放内存。

  4. 避免裸指针:尽量不使用裸指针来管理内存。如果必须使用裸指针,请确保在合适的时机使用delete或delete[]来释放内存。

  5. 使用内存池:对于频繁的小块内存分配和释放,可以使用内存池来管理内存,这样可以减少内存碎片,提高内存分配和释放的效率。

  6. 利用标准库算法:使用标准库提供的算法(如std::sort、std::for_each等)来操作容器,这些算法不会导致容器内部的内存泄漏。

  7. 异常安全:确保容器操作在异常抛出时仍然安全,避免因异常导致的资源泄露。

  8. 定期代码审查:通过代码审查来识别潜在的内存泄漏问题,并使用内存泄漏检测工具(如 Valgrind、Visual Studio 的内存检测工具等)来测试代码。

  9. 避免循环引用:在使用智能指针时,注意避免循环引用,这会导致引用计数永远不为零,从而造成内存泄漏。

  10. 析构函数为虚:如果你的类继承自某个容器类,确保基类的析构函数为虚,以确保在删除派生类对象时能正确调用基类的析构函数释放资源。

通过这些方法,可以有效地减少内存泄漏的风险,并提高程序的稳定性和可靠性。


关于作者

  • 微信公众号:WeSiGJ
  • GitHub:https://github.com/wesigj/cplusplusboys
  • CSDN:https://blog.csdn.net/wesigj
  • 微博:
  • -版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
WeSiGJ
微信公众号
共同分享,共同交流, 共同学习!
注:本文转载自blog.csdn.net的WeSiGJ的文章"https://blog.csdn.net/wesigj/article/details/142706873"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top