C++ STL常见容器

vector

  • vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。

vector的迭代器

vector维护的是一个连续线性空间,所以不论其元素型别为何,普通指针都可以作为vector的迭代器而满足所有必要条件,因为vector迭代器需要的操作行为,如operator*operator->等,普通指针天生就具备。vector支持随机存取。

cpp 复制代码
template <class T, class Alloc = alloc>
class vector
{
// ...
public:
    typedef T value_type;
    typedef value_type* iterator; // vector的迭代器是普通指针
// ...
};

若客端代码如下,则ivite的型别其实就是int*svite的型别其实就是Shape*

cpp 复制代码
vector<int>::iterator ivite;
vector<Shape>::iterator svite;

vector的数据结构

为了降低空间配置时的速度成本,vector实际配置的大小可能比客户端需求量大一些,以备将来可能的扩充。这便是容量(capacity),一个vector的容量永远大于或者等于其大小。

cpp 复制代码
template <class T, class Alloc = alloc>
class vector
{
// ...
protected:
    iterator start; // 目前使用空间的头
    iterator finish; // 目前使用空间的尾
    iterator end_of_storage; // 目前可用空间的尾
// ...
};

运用start、finish、end_of_storage三个迭代器,便可轻易地提供首尾标示、大小等机能。

cpp 复制代码
class vector
{
// ...
public:
    iterator begin() { return start; }
    iterator end() { return finish; }
    size_type size() const { return size_type(end() - begin()); }
    size_type capacity() const { return size_type(end_of_storage - begin()); }
    bool empty() const { return begin() == end(); }
    reference operator[] (size_type n) { return *(begin() + n); }
    reference front() { return *begin(); }
    reference back() { return *(end() - 1); }
// ...
};

vector的构造和内存管理

vector提供许多constructors,其中一个允许我们指定空间大小及初值。

cpp 复制代码
// 构造函数,允许指定vector大小n和初值value
vector(size_type n, const T& value) { fill_initialize(n, value); }

// 填充并予以初始化
void fill_initialize(size_type n, const T& value)
{
    start = allocate_and_fill(n, value);
    finish = start + n;
    end_of_storage = finish;
}

// 配置而后填充
iterator allocate_and_fill(size_type n, const T& x)
{
    iterator result = data_allocator::allocate(n); // 配置n个元素空间
    uninitialized_fill_n(result, n, x);
    return result;
}

push_back()将新元素插入于vector尾端时,该函数首先检查是否还有备用空间,如果有就直接在备用空间上构造元素,并调整迭代器finish,使vector变大;如果没有备用空间就扩充空间(2倍扩容)。

cpp 复制代码
void push_back(const T& x)
{
    if (finish != end_of_storage)
    {
        // 有备用空间
        construct(finish, x);
        ++finish; // 调整水位高度
    }
    else
    {
        // 无备用空间,需要扩容
        insert_aux(end(), x);
    }
}

template <class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x)
{
    if (finish != end_of_storage)
    {
        // 有备用空间
        construct(finish, *(finish - 1));
        ++finish; // 调整水位
        T x_copy = x;
        copy_backward(position, finish - 2, finish - 1);
        *positioon = x_copy;
    }
    else
    {
        // 已无备用空间
        const size_type old_size = size();
        // 若原大小为0,则配置1个元素大小
        // 若原大小不为0,则二倍扩容
        const size_type len = old_size != 0 ? 2 * old_size : 1;

        iterator new_start = data_allocator::allocate(len); // 实际配置
        construct(new_finish, x); // 为新元素设定初值x
        ++new_finish;
        // 将原vector的备用空间中的内容也拷贝过来(?)
        new_finish = uninitialized_copy(position, finish, new_finish);
    }
    catch(...)
    {
        destroy(new_start, new_finish);
        data_allocator::deallocate(new_start, len);
        throw;
    }

    // 析构并释放原vector
    destroy(begin(), end());
    deallocate();

    // 调整迭代器,指向新vector
    start = new_start;
    finish = new_finish;
    end_of_storage = new_start + len;
}
  • 所谓动态增加大小,并不是在原空间之后连续新空间(因为无法保证原空间之后尚有可配置的空间),而是以原大小的两倍另外配置一块较大空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。

list

  • 相比于vector的连续线性空间,list就显得复杂许多。**它的好处是每次插入或删除一个元素,就配置或释放一个元素空间。**list对于空间的运用绝对精准,一点也不浪费。

list的节点

cpp 复制代码
// 实质是一个双向链表
template <class T>
struct __list_node
{
    typedef void* void_pointer;
    void_pointer prev; // 型别为void*
    void_pointer next;
    T data;
};
  • list不能用普通指针作为迭代器,因为其节点不保证在存储空间中连续存在。

list的数据结构

SGI list不仅是一个双向链表,而且还是一个环状双向链表。(只需一个指针,便可以完整表现整个链表。)

cpp 复制代码
template <class T, class Alloc = alloc>
class list
{
protected:
    typedef __list_node<T> list_node;
public:
    typedef list_node* link_type;
protected:
    link_type node; // 只要一个指针,便可表示整个环状双向链表
};

deque

  • vector是单向开口的连续线性空间,deque则是双向开口的连续线性空间。
  • deque允许常数时间内对头端进行元素的插入与移除操作。
  • deque没有所谓容量(capacity)观念,它是动态地分段连续空间组合而成。
  • 除非必要,应尽可能选用vector而非deque。

deque逻辑上是连续空间,实际是由一段一段的定量连续空间构成。一旦有必要在deque的前端或尾端增加新空间,便配置一段定量连续空间,串接在整个deque的头端或尾端。无需维护整体连续,但代价是复杂的迭代器架构。

deque的数据结构

deque的迭代器(较为复杂)必须能够指出分段连续空间(亦即缓冲区)在哪里;必须能够判断自己是否已经处于其所在缓冲区的边缘,如果是,一旦前进或后退时就必须跳跃至下一个或上一个缓冲区。

cpp 复制代码
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque
{
public:
    typedef T value_type;
    typedef value_type* pointer;
    typedef size_t size_type;

public:
    typedef __deque_iterator<T, T&, T*, BufSiz> iterator;

protected:
    typedef pointer* map_pointer;
protected:
    iterator start; // 第一个节点
    iterator finish; // 最后一个节点

    map_pointer map; // 指向map

    size_type map_size; // map内有多少指针
};

stack

  • stack是一种"先进后出"(FILO)的数据结构。
  • stack除了最顶端外,没有任何其它方法可以存取stack的其它元素。换言之,stack不允许所有遍历行为。
  • stack不提供走访功能,也不提供迭代器。

stack的实现

stack以底部容器完成其所有工作,因此STL stack往往不被归类为container,而被归类为container adapter。

cpp 复制代码
template <class T, class Sequence = deque<T>>
class stack
{
    // ...
public:
    typedef typename Sequence::value_type value_type;
    typedef typename Sequence::size_type size_type;
    typedef typename Sequence::reference reference;
    typedef typename Sequence::const_reference const_reference;
protected:
    Sequence c; // 底层容器
public:
    // 完全利用Sequence c的操作,完成stack的操作
    bool empty() const { return c.empty(); }
    size_type size() const { return c.size(); }
    reference top() { return c.back(); }
    const_reference top() const { return c.back(); }
    void push(const value_type& x) { c.push_back(x); }
    void pop() { c.pop_back(); }
};

queue

  • queue是一种"先进先出"(FIFO)的数据结构。
  • queue除了最顶端可以取出、最低端可以加入外,没有任何其它方法可以存取queue的其它元素。换言之,queue不允许所有遍历行为。
  • queue亦不提供迭代器。

queue的实现

queue以底部容器完成其所有工作,因此STL queue往往不被归类为container,而被归类为container adapter。

cpp 复制代码
template <class T, class Sequence = deque<T>>
class queue
{
    // ...
public:
    typedef typename Sequence::value_type value_type;
    typedef typename Sequence::size_type size_type;
    typedef typename Sequence::reference reference;
    typedef typename Sequence::const_reference const_reference;
protected:
    Sequence c; // 底层容器
public:
    // 完全利用Sequence c的操作,完成queue的操作
    bool empty() const { return c.empty(); }
    size_type size() const { return c.size(); }
    reference front() { return c.front(); }
    const_reference front() const { return c.front(); }
    reference back() { return c.back(); }
    const_reference back() const { return c.back(); }
    void push(const value_type& x) { c.push_back(x); }
    void pop() { c.pop_front(); }
};

priority_queue

  • 拥有权值观念的queue,其内的元素并非按照被推入的次序排列,而是自动依照元素的权值排列。
  • priority_queue的所有元素,进出都有一定的规则,只有queue顶端的元素才有机会被外界取用。

priority_queue的实现

cpp 复制代码
template <class T, class Sequence = vector<T>,
        class Compare = less<typename Sequence::value_type> >
class priority_queue
{
public:
    typedef typename Sequence::value_type value_type;
    typedef typename Sequence::size_type size_type;
    typedef typename Sequence::reference reference;
    typedef typename Sequence::const_reference const_reference;
protected:
    Sequence c; // 底层容器
    Compare comp; // 元素大小比较标准
public:
    priority_queue() : c() {}
    explicit priority_queue(const Compare& x) : c(), comp(x) {}

    template <class InputIterator>
    priority_queue(InputIterator first, InputIterator last, const Compare& x) : c(first, last), comp(x) { make_heap(c.begin(), c.end(), comp); }
    template <class InputIterator>
    priority_queue(InputIterator first, InputIterator last) : c(first, last) { make_heap(c.begin(), c.end(), comp); }

    bool empty() const { return c.empty(); }
    size_type size() const { return c.size(); }
    const_reference top() const {return c.front(); }
    void push(const value_type& x)
    {
        // 将新元素推入末端
        c.push_back(x);
        // 重排heap
        push_heap(c.begin(), c.end(), comp);
    }
    void pop()
    {
        pop_heap(c.begin(), c.end(), comp);
        c.pop_back();
    }
};

map和set

map属于关联容器与序列容器有着根本性的不同,序列容器的元素是按照在容器中的位置来顺序保存和访问的,而关联容器的元素是按关键元素来保存和访问的,都是基于红黑树实现的。

  • 每个节点不是红色就是黑色。
  • 根节点为黑色。
  • 如果节点为红,其子节点必须为黑。
  • 任一节点至NULL(树尾端)的任何路径,所含之黑节点数必须相同。
相关推荐
2401_8454174521 分钟前
C++ string类
java·开发语言·c++
Y.O.U..35 分钟前
力扣HOT100——560.和为k的子数组
数据结构·c++·算法·leetcode
wuqingshun31415940 分钟前
经典算法 判断一个图中是否有环
java·开发语言·数据结构·c++·算法·蓝桥杯·深度优先
编程见习者1 小时前
OpenCV的详细介绍与安装(一)
c++·人工智能·opencv·计算机视觉
邪恶的贝利亚1 小时前
C++ 基础深入剖析:编译、内存与面向对象编程要点解析
开发语言·c++
ChoSeitaku1 小时前
NO.93十六届蓝桥杯备战|图论基础-拓扑排序|有向无环图|AOV网|摄像头|最大食物链计数|杂物(C++)
c++·蓝桥杯·图论
Dream it possible!1 小时前
CCF CSP 第36次(2024.12)(1_移动_C++)
c++·ccf csp·csp
HackerKevn2 小时前
【项目】构建高性能多线程内存池:简化版 tcmalloc 实现指南
c++·高并发内存池·tcmalloc·池化技术
珊瑚里的鱼2 小时前
【双指针】专题:LeetCode 202题解——快乐数
开发语言·c++·笔记·算法·leetcode·职场和发展
2401_858286113 小时前
CD27.【C++ Dev】类和对象(18)友元和内部类
开发语言·c++·类和对象