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

系列文章目录

点击直达——文章总目录



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

Overview

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

1.std::deque 定义

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

std::deque的定义如下:

#include <deque>

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.1.关键点

  1. 模板参数

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

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

    • iteratorconst_iterator:分别表示可变和不可变的迭代器。
    • reverse_iteratorconst_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 <iostream>
#include <deque>

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;
}

2.4.输出结果

Deque contains: 3 5 10 20 30 
Deque contains after pop_front: 5 10 20 30 

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_backpush_frontpop_backpop_frontinserterase等成员函数来修改容器。

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

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

  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::setstd::map

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

4.6. std::unordered_setstd::unordered_map

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

4.7. std::array

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

4.8. std::tuple

  • 用途:存储固定数量的不同类型数据。
  • 高效使用
    • 使用std::get<N>()访问元素,其中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_ptrstd::shared_ptrstd::weak_ptr自动管理内存,它们会在适当的时候自动释放内存,从而减少内存泄漏的风险。

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

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

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

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

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

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

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

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

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

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


关于作者

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WeSiGJ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值