cpp - STL 常用操作

78 阅读27分钟

cpp - STL 常用操作

一、序列容器(Sequential Containers)

序列容器按照元素插入的顺序存储数据,支持位置访问。

array

array 是固定大小的数组容器,在编译时确定大小,提供了类似 C 数组的性能和 STL 容器的接口。

内存布局: 连续内存存储。元素直接存储在对象内部(栈上或作为类成员),没有额外的堆分配开销。大小在编译时确定,无法动态改变。

array<int, 5> 对象: [元素0][元素1][元素2][元素3][元素4]
                    ← 5 * sizeof(int) 字节 →

时间复杂度:

  • 随机访问([]):O(1) - 连续内存,直接偏移
  • 查找:O(n) - 需要遍历
  • 不支持插入和删除操作

空间复杂度: O(n),无额外开销,大小固定

初始化

int iArr[10];
constexpr len0 = 10; int jArr[len];
const len = 10; int jArr[len]; // 错误。len 不是常量表达式
string strs[get_size()]; 当 get_size(); // 返回常量表达式时正确,否则错误

array 是固定大小的容器,不能增加元素,删除元素

array<int, 5> arr = {1, 2, 3, 4, 5};
arr[2] = 10;            // {1, 2, 10, 4, 5}
arr.at(3) = 20;         // {1, 2, 10, 20, 5}

array<int, 5> arr = {1, 2, 3, 4, 5};
int first = arr[0];              // 1
int last = arr.back();          // 5
int val = arr.at(2);            // 3 (带边界检查)
auto it = find(arr.begin(), arr.end(), 3);  // 返回迭代器
size_t size = arr.size();       // 5
bool empty = arr.empty();       // false

list

list 是双向链表容器,支持在任意位置快速插入和删除,但不支持随机访问。

内存布局: 双向链表。每个节点包含数据和两个指针(prev 和 next),节点在堆上独立分配,非连续存储。

list 对象: [head] → 节点1 ⇄ 节点2 ⇄ 节点3 ⇄ 节点4 ← [tail]
                    ↓       ↓       ↓       ↓
                  [prev]  [prev]  [prev]  [prev]
                  [data]  [data]  [data]  [data]
                  [next]  [next]  [next]  [next]

每个节点独立分配,内存不连续

时间复杂度:

  • 头部/尾部插入删除(push_front/push_back/pop_front/pop_back):O(1) - 直接修改头尾指针
  • 中间插入删除(已知迭代器位置):O(1) - 只需修改前后节点的指针
  • 随机访问:不支持 - 必须从头或尾遍历
  • 查找:O(n) - 需要遍历链表
  • 排序(sort):O(n log n) - 归并排序

空间复杂度: O(n),每个节点额外存储两个指针

初始化

list<int> l1;                         // 空列表
list<int> l2 = {1, 2, 3, 4, 5};      // 使用初始化列表
list<int> l3(5, 10);                 // 包含5个值为10的元素
list<int> l4(l2);                    // 复制构造

list<int> lst = {1, 2, 3};
lst.push_back(4);                    // 在末尾添加: {1, 2, 3, 4}
lst.push_front(0);                   // 在开头添加: {0, 1, 2, 3, 4}
lst.insert(next(lst.begin()), 5);    // 在指定位置插入: {0, 5, 1, 2, 3, 4}
lst.emplace_back(6);                 // 在末尾构造: {0, 5, 1, 2, 3, 4, 6}
lst.emplace_front(-1);               // 在开头构造: {-1, 0, 5, 1, 2, 3, 4, 6}

list<int> lst = {1, 2, 3, 4, 5};
lst.pop_back();                      // 删除末尾元素
lst.pop_front();                     // 删除首个元素
lst.erase(next(lst.begin()));        // 删除指定位置的元素
lst.remove(3);                       // 删除所有值为3的元素
lst.clear();                         // 清空列表

list<int> lst = {1, 2, 3, 4, 5};
auto it = next(lst.begin(), 2);      // 获取第三个元素的迭代器
*it = 10;                            // 修改元素值
lst.sort();                          // 排序
lst.reverse();                       // 反转
lst.unique();                        // 删除连续重复元素

list<int> lst = {1, 2, 3, 4, 5};
int first = lst.front();             // 获取第一个元素
int last = lst.back();               // 获取最后一个元素
size_t size = lst.size();            // 获取元素个数
bool empty = lst.empty();            // 检查是否为空
auto it = find(lst.begin(), lst.end(), 3);  // 查找元素

string

string 是 C++ 标准库提供的动态字符串类,用于处理可变长度的字符序列。

内存布局: 动态分配的连续内存。通常采用 SSO(Small String Optimization)优化:短字符串(通常 ≤15 字节)直接存储在对象内部,避免堆分配;长字符串则在堆上分配连续内存。

短字符串 (SSO):
string 对象: [字符数据内联存储...][长度][容量]

长字符串:
string 对象: [指针] → 堆内存: [字符数据...]
             [长度]
             [容量]

时间复杂度:

  • 尾部插入(push_back/append):均摊 O(1) - 容量不足时需要重新分配并拷贝
  • 头部/中间插入:O(n) - 需要移动插入点之后的所有字符
  • 删除:O(n) - 需要移动删除点之后的所有字符
  • 随机访问([]):O(1) - 连续内存直接索引
  • 查找(find):O(n*m) - 子串查找,n 为字符串长度,m 为子串长度
  • 拼接(+/+=):O(n) - 需要拷贝字符

空间复杂度: O(n),额外维护长度和容量信息

初始化

string s0;
string s1 = "value";
string s4 = string("value");

string s2 = s1;
string s2(s1);

string s3 = string(4, "c") // "cccc"

// 追加
string s = "hello";
s += " world";      // "hello world"
s.append(" !");     // "hello world !"
s.push_back('!');   // "hello world !!"
s.insert(5, " my"); // "hello my world !!"

string s = "hello world";
s.erase(5, 6);      // 从位置5开始删除6个字符: "hello"
s.erase(s.begin() + 4);  // 删除迭代器指向的单个字符
s.erase(s.begin(), s.begin() + 4);  // 删除迭代器范围内的字符
s.pop_back();       // 删除最后一个字符: "hell"

string s = "hello world";
s.replace(6, 5, "there"); // 从位置6开始的5个字符替换为"there": "hello there"
s[0] = 'H';              // 修改单个字符: "Hello there"

string s = "hello world";
char c = s[0];           // 访问单个字符: 'h'
string sub = s.substr(6, 5); // 从位置6开始截取5个字符: "world"
size_t pos = s.find("world"); // 查找子串,返回首次出现位置: 6
                              // 如果未找到返回 string::npos
size_t pos2 = s.find_first_not_of("helo "); // 查找第一个不在给定字符集中的字符位置: 6 ('w')
size_t pos3 = s.find_last_of("o");     // 查找最后一个'o'的位置: 7
size_t pos4 = s.find_first_of("o");    // 查找第一个'o'的位置: 4
size_t pos5 = s.rfind("l");            // 从右向左查找'l'首次出现的位置: 9
size_t pos6 = s.find_last_not_of(" "); // 查找最后一个不是空格的字符位置: 10

vector

vector 是动态数组容器,支持快速随机访问和尾部高效插入删除。

内存布局: 连续内存存储。所有元素存储在一块连续的堆内存中,维护三个指针:start(起始位置)、finish(已用空间末尾)、end_of_storage(已分配空间末尾)。

vector 对象: [start] → 堆内存: [元素0][元素1][元素2][...][未使用空间...]
             [finish] --------→ ↑
             [end_of_storage] → ↑

size = finish - start
capacity = end_of_storage - start

时间复杂度:

  • 尾部插入(push_back):均摊 O(1) - 容量不足时扩容(通常 2 倍),需要拷贝所有元素
  • 尾部删除(pop_back):O(1) - 直接移动 finish 指针
  • 头部/中间插入:O(n) - 需要移动插入点之后的所有元素
  • 头部/中间删除:O(n) - 需要移动删除点之后的所有元素
  • 随机访问([]):O(1) - 连续内存,直接指针偏移
  • 查找:O(n) - 需要遍历

空间复杂度: O(n),通常会预留额外空间(capacity > size)以减少扩容次数

初始化

vector<T> v0;
vector<T> v1 = {a, b, c};
vector<T> v1{a, b, c};
vector<T> v2 = v1;
vector<T> v2(v1);
vector<T> v3(n, val); // 含有 n 个 val 的vector

vector<int> v = {1, 2, 3};
v.push_back(4);        // {1, 2, 3, 4}
v.insert(v.begin(), 0);  // {0, 1, 2, 3, 4}
v.insert(v.begin() + 2, 5);  // {0, 1, 5, 2, 3, 4}
v.emplace_back(6);     // {0, 1, 5, 2, 3, 4, 6}

vector<int> v = {1, 2, 3, 4, 5};
v.pop_back();          // {1, 2, 3, 4}
v.erase(v.begin());    // {2, 3, 4}
v.erase(v.begin(), v.begin() + 2);  // {4}
v.clear();            // {}

vector<int> v = {1, 2, 3, 4, 5};
v[2] = 10;            // {1, 2, 10, 4, 5}
v.at(3) = 20;         // {1, 2, 10, 20, 5}

vector<int> v = {1, 2, 3, 4, 5};
int first = v[0];              // 1
int last = v.back();          // 5
int val = v.at(2);            // 3 (带边界检查)
auto it = find(v.begin(), v.end(), 3);  // 返回迭代器
size_t size = v.size();       // 5
bool empty = v.empty();       // false

伪代码

#include <iostream>
#include <algorithm>
#include <sstream>
#include <string>
#include <stdexcept>

template <typename T>
class Vector {
    private:
        T* elements_ = nullptr;
        size_t capacity_ = 0;
        size_t size_ = 0;

    public:
        Vector() = default;

        ~Vector() {
            delete[] elements_;
        }

        // 拷贝构造函数
        Vector(const Vector& o) {
            capacity_ = o.capacity_;
            size_ = o.size_;

            elements_ = new T[capacity_];
            std::copy(o.elements_, o.elements_ + size_, elements_);
        }

        // 拷贝赋值操作符
        Vector& operator=(const Vector& o) {
            if (this != &o) {
                delete[] elements_;
                capacity_ = o.capacity_;
                size_ = o.size_;
                elements_ = new T[capacity_];
                std::copy(o.elements_, o.elements_ + size_, elements_);
            }
        }

        void push_back(const T& value) {
            if (size_ == capacity_) {
                reserve(capacity_ == 0 ? 1 : capacity_ << 1);
            }
            elements_[size_++] = value;
        }

        size_t getSize() const {
            return size_;
        }

        size_t getCapacity() const {
            return capacity_;
        }

        T& operator[](size_t index) {
            if (index >= size_) {
                throw std::out_of_range("index out of range");
            }
            return elements_[index];
        }

        const T& operator[](size_t index) const {
            if (index >= size_) {
                throw std::out_of_range("index out of range");
            }
            return elements_[index];
        }

        void insert(size_t index, const T& value) {
            if (index > size_) {
                throw std::out_of_range("index out range");
            }
            if (size_ == capacity_) {
                reserve(capacity_ == 0 ? 1 : capacity_ << 1);
            }
            for (size_t i = size_; i > index; --i) {
                elements_[i] = elements_[i - 1];
            }
            elements_[index] = value;
            ++size_;
        }

        void pop_back() {
            if (size_ > 0) {
                --size_;
            }
        }

        void clear() {
            size_ = 0;
        }

        T* begin() {
            return elements_;
        }

        T* end() {
            return elements_ + size_;
        }

        const T* begin() const {
            return elements_;
        }

        const T* end() const {
            return elements_ + size_;
        }

        void printElements() const {
            for (size_t i = 0; i < size_; i++) {
                std::cout << elements_[i] << " ";
            }
            std::cout << std::endl;
        }

    private:
        void reserve(size_t newCapacity) {
            if (newCapacity > capacity_) {
                auto* newElements = new T[newCapacity];
                std::copy(elements_, elements_ + size_, newElements);
                delete elements_;
                elements_ = newElements;
                capacity_ = newCapacity;
            }
        }

};

int main()
{
    // 创建一个 Vector 对象
    Vector<int> Vector;

    int N;
    std::cin >> N;
    // 读走回车
    getchar();

    std::string line;
    for (int i = 0; i < N; i++)
    {
        // 读取整行
        std::getline(std::cin, line);
        std::istringstream iss(line);
        std::string command;
        iss >> command;

        if (command == "push")
        {
            int value;
            iss >> value;
            Vector.push_back(value);
        }
        else if (command == "print")
        {
            if (Vector.getSize() == 0) {
                std::cout << "empty" << std::endl;
                continue;
            }
            Vector.printElements();
        }
        else if (command == "size")
        {
            std::cout << Vector.getSize() << std::endl;
        }
        else if (command == "get")
        {
            int index;
            iss >> index;
            std::cout << Vector[index] << std::endl;
        }
        else if (command == "insert")
        {
            int index, value;
            iss >> index >> value;
            Vector.insert(index, value);
        }
        else if (command == "pop")
        {
            Vector.pop_back();
        }
        else if (command == "iterator")
        {
            if (Vector.getSize() == 0)
            {
                std::cout << "empty" << std::endl;
                continue;
            }
            for (auto it = Vector.begin(); it != Vector.end(); ++it)
            {
                std::cout << *it << " ";
            }
            std::cout << std::endl;
        }
        else if (command == "foreach")
        {
            if (Vector.getSize() == 0)
            {
                std::cout << "empty" << std::endl;
                continue;
            }
            for (const auto &element : Vector)
            {
                std::cout << element << " ";
            }
            std::cout << std::endl;
        }
        else if (command == "clear")
        {
            Vector.clear();
        }
    }
    return 0;
}

deque

deque(double-ended queue,双端队列)是一个支持在两端高效插入和删除的动态数组容器。

内存布局: 分段连续存储(segmented array)。deque 由多个固定大小的连续内存块组成,通过中央索引数组(map)管理这些内存块。每个内存块存储一定数量的元素,map 数组存储指向这些内存块的指针。

map:  [ptr0] [ptr1] [ptr2] [ptr3] ...
        ↓      ↓      ↓      ↓
      [###] [###] [###] [###]
       块0   块1   块2   块3

时间复杂度:

  • 头部/尾部插入删除:O(1) - 只需在首/尾块操作,必要时分配新块
  • 随机访问:O(1) - 通过索引计算:块索引 = index / 块大小,块内偏移 = index % 块大小
  • 中间插入删除:O(n) - 需要移动插入点到最近一端之间的所有元素
  • 查找:O(n) - 需要遍历

空间复杂度: O(n),额外维护 map 数组和多个内存块

初始化

deque<int> d1;                        // 空双端队列
deque<int> d2 = {1, 2, 3, 4, 5};     // 使用初始化列表
deque<int> d3(5, 10);                // 包含5个值为10的元素
deque<int> d4(d2);                   // 复制构造

deque<int> dq = {1, 2, 3};
dq.push_back(4);                     // 在末尾添加: {1, 2, 3, 4}
dq.push_front(0);                    // 在开头添加: {0, 1, 2, 3, 4}
dq.insert(dq.begin() + 2, 5);        // 在指定位置插入: {0, 1, 5, 2, 3, 4}
dq.emplace_back(6);                  // 在末尾构造: {0, 1, 5, 2, 3, 4, 6}
dq.emplace_front(-1);                // 在开头构造: {-1, 0, 1, 5, 2, 3, 4, 6}

deque<int> dq = {1, 2, 3, 4, 5};
dq.pop_back();                       // 删除末尾元素
dq.pop_front();                      // 删除首个元素
dq.erase(dq.begin() + 1);            // 删除指定位置的元素
dq.erase(dq.begin(), dq.begin() + 2); // 删除一个范围的元素
dq.clear();                          // 清空双端队列

deque<int> dq = {1, 2, 3, 4, 5};
dq[2] = 10;                          // 使用下标修改元素
dq.at(3) = 20;                       // 使用at()修改元素(带边界检查)

deque<int> dq = {1, 2, 3, 4, 5};
int first = dq.front();              // 获取第一个元素
int last = dq.back();                // 获取最后一个元素
int val = dq[2];                     // 使用下标访问
int val_safe = dq.at(2);             // 使用at()访问(带边界检查)
size_t size = dq.size();             // 获取元素个数
bool empty = dq.empty();             // 检查是否为空
auto it = find(dq.begin(), dq.end(), 3);  // 查找元素

二、关联容器(Associative Containers)

关联容器基于键值进行存储和查找,使用哈希表实现,提供快速的查找性能。

unordered_map

unordered_map 是基于哈希表实现的键值对容器,提供快速的查找、插入和删除操作。

内存布局: 哈希表 + 链表法解决冲突。维护一个桶数组(bucket array),每个桶是一个链表,存储哈希值相同的元素。通过哈希函数将键映射到桶索引。

桶数组:  [0][key1:val1][key2:val2] → nullptr
        [1] → nullptr
        [2][key3:val3] → nullptr
        [3][key4:val4][key5:val5] → nullptr
        ...

负载因子 = 元素数量 / 桶数量
当负载因子超过阈值(默认1.0)时,rehash 扩容

时间复杂度:

  • 插入/删除/查找:平均 O(1),最坏 O(n) - 哈希冲突导致链表过长
  • 遍历:O(n) - 需要遍历所有桶和链表节点
  • rehash:O(n) - 需要重新计算所有元素的哈希值并插入新桶数组

空间复杂度: O(n),额外维护桶数组和链表节点

初始化

unordered_map<string, int> map1;
unordered_map<string, int> map2 = {{"apple", 1}, {"banana", 2}};
unordered_map<string, int> map3(map2);

增/改

unordered_map<string, int> map;
map["apple"] = 1;              // 插入或更新键值对
map.insert({"banana", 2});     // 仅插入,如果键已存在则不更新
map.insert_or_assign("apple", 3); // 插入新元素或更新现有值
map.emplace("orange", 4);      // 原地构造并插入元素

unordered_map<string, int> map = {{"apple", 1}, {"banana", 2}, {"orange", 3}};
map.erase("apple");           // 删除指定键值对
map.erase(map.begin());       // 使用迭代器删除
map.clear();                  // 清空所有元素

unordered_map<string, int> map = {{"apple", 1}, {"banana", 2}};
int value = map["apple"];     // 访问元素(如果键不存在会插入默认值)
int value2 = map.at("apple"); // 访问元素(如果键不存在会抛出异常)
auto it = map.find("banana"); // 查找元素,返回迭代器
bool exists = map.count("apple") > 0;  // 检查键是否存在
size_t size = map.size();     // 获取元素个数
bool empty = map.empty();     // 检查是否为空

unordered_set

unordered_set 是基于哈希表实现的集合容器,存储唯一元素,提供快速的查找、插入和删除操作。

内存布局: 哈希表 + 链表法解决冲突。与 unordered_map 类似,但只存储键(元素),不存储值。维护桶数组,每个桶是链表。

桶数组:  [0][元素1][元素2] → nullptr
        [1] → nullptr
        [2][元素3] → nullptr
        [3][元素4][元素5] → nullptr
        ...

负载因子 = 元素数量 / 桶数量
超过阈值时 rehash 扩容

时间复杂度:

  • 插入/删除/查找:平均 O(1),最坏 O(n) - 哈希冲突导致链表过长
  • 遍历:O(n) - 需要遍历所有桶和链表节点
  • rehash:O(n) - 需要重新计算所有元素的哈希值并插入新桶数组

空间复杂度: O(n),额外维护桶数组和链表节点

初始化

unordered_set<int> s1;                    // 空集合
unordered_set<int> s2 = {1, 2, 3, 4, 5}; // 使用初始化列表
unordered_set<int> s3(s2);               // 复制构造

unordered_set<int> s = {1, 2, 3};
s.insert(4);                    // 插入单个元素: {1, 2, 3, 4}
s.insert({5, 6});              // 插入多个元素: {1, 2, 3, 4, 5, 6}
s.emplace(7);                  // 原地构造并插入元素: {1, 2, 3, 4, 5, 6, 7}

// 检查是否存在,不存在则插入
auto [it, inserted] = s.insert(8);  // 返回 pair<iterator, bool>
if (inserted) {
    cout << "元素 8 被插入" << endl;
} else {
    cout << "元素 8 已存在" << endl;
}

// 使用 emplace 也可以实现类似功能
auto [it2, inserted2] = s.emplace(9);  // 返回 pair<iterator, bool>
if (inserted2) {
    cout << "元素 9 被插入" << endl;
} else {
    cout << "元素 9 已存在" << endl;
}

unordered_set<int> s = {1, 2, 3, 4, 5};
s.erase(3);                    // 删除指定元素: {1, 2, 4, 5}
s.erase(s.begin());           // 使用迭代器删除: {2, 4, 5}
s.erase(s.begin(), s.end());  // 删除一个范围的元素: {}
s.clear();                    // 清空集合

unordered_set<int> s = {1, 2, 3, 4, 5};
bool exists = s.count(3) > 0;  // 检查元素是否存在
auto it = s.find(2);          // 查找元素,返回迭代器
size_t size = s.size();       // 获取元素个数
bool empty = s.empty();       // 检查是否为空

其他操作

unordered_set<int> s = {1, 2, 3, 4, 5};
s.bucket_count();            // 返回桶的数量
s.load_factor();            // 返回负载因子
s.max_load_factor();        // 返回最大负载因子
s.rehash(10);               // 重新设置桶的数量
s.reserve(100);             // 预留空间

遍历

unordered_set<int> s = {1, 2, 3, 4, 5};
// 使用迭代器遍历
for (auto it = s.begin(); it != s.end(); ++it) {
    cout << *it << " ";
}

// 使用范围for循环遍历
for (const auto& elem : s) {
    cout << elem << " ";
}

unordered_set 的特点:

  1. 无序存储,元素顺序不固定
  2. 不允许重复元素
  3. 查找、插入、删除操作的平均时间复杂度为 O(1)
  4. 使用哈希表实现
  5. 不支持下标访问
  6. 不支持排序操作

三、容器适配器(Container Adapters)

容器适配器是基于其他容器实现的特殊接口封装,不直接存储数据,而是对底层容器进行包装,提供特定的访问模式。

queue

queue 是先进先出(FIFO, First In First Out)的容器适配器,只允许在尾部插入元素,在头部删除元素。

// queue 的简化实现原理
template<typename T, typename Container = deque<T>>
class queue {
private:
    Container c;  // 底层容器

public:
    void push(const T& value) {
        c.push_back(value);  // 调用底层容器的 push_back
    }

    void pop() {
        c.pop_front();  // 调用底层容器的 pop_front
    }

    T& front() { return c.front(); }
    T& back() { return c.back(); }
    size_t size() const { return c.size(); }
    bool empty() const { return c.empty(); }
};

内存布局: 基于底层容器实现(默认使用 deque,也可以使用 list)。queue 本身只是一个接口封装,真正的数据存储在底层容器中。

queue 对象: [底层容器对象]
            只暴露 front/back/push/pop 接口

底层容器(deque):  [元素0][元素1][元素2][元素3]
                   ↑ front        ↑ back
                   出队           入队

为什么默认使用 deque:

  • deque 两端插入/删除都是 O(1),完美符合 queue 的需求
  • list 也可以,但每个节点有额外的指针开销
  • vector 不行,因为头部删除是 O(n)

时间复杂度:

  • 入队(push):O(1) - 在尾部插入
  • 出队(pop):O(1) - 在头部删除
  • 访问队首(front):O(1) - 直接访问
  • 访问队尾(back):O(1) - 直接访问
  • 不支持随机访问和遍历

空间复杂度: O(n),取决于底层容器

初始化

queue<int> q1;                        // 空队列(默认使用 deque)
queue<int, list<int>> q2;            // 使用 list 作为底层容器
queue<int> q3(deque<int>{1, 2, 3});  // 从底层容器构造

queue<int> q;
q.push(1);                           // 入队: {1}
q.push(2);                           // 入队: {1, 2}
q.push(3);                           // 入队: {1, 2, 3}
q.emplace(4);                        // 原地构造并入队: {1, 2, 3, 4}

queue<int> q;
q.push(1); q.push(2); q.push(3);
q.pop();                             // 出队(删除队首元素): {2, 3}
q.pop();                             // 出队: {3}
// 注意:pop() 不返回值,需要先用 front() 获取值再 pop()

queue<int> q;
q.push(1); q.push(2); q.push(3);
int front_val = q.front();           // 获取队首元素: 1
int back_val = q.back();             // 获取队尾元素: 3
size_t size = q.size();              // 获取元素个数: 3
bool empty = q.empty();              // 检查是否为空: false

stack

stack 是后进先出(LIFO, Last In First Out)的容器适配器,只允许在顶部插入和删除元素。

容器适配器特性:

stack 默认使用 deque 作为底层容器,也可以使用 vector 或 list。stack 只需要支持尾部操作(push_back/pop_back/back),因此这三种容器都适用。

// 默认使用 deque
stack<int> s1;
// 等价于
stack<int, deque<int>> s1;

// 也可以使用 vector
stack<int, vector<int>> s2;

// 也可以使用 list
stack<int, list<int>> s3;

内存布局: 基于底层容器实现。stack 本身只是一个接口封装,限制只能访问顶部元素。

stack 对象: [底层容器对象]
            只暴露 top/push/pop 接口

底层容器(deque):  [元素0][元素1][元素2][元素3]
                                        ↑ top
                                      入栈/出栈

为什么默认使用 deque:

  • deque 尾部插入/删除都是 O(1)
  • deque 不需要像 vector 那样频繁重新分配内存
  • list 也可以,但有额外的指针开销

时间复杂度:

  • 入栈(push):O(1) - 在顶部插入
  • 出栈(pop):O(1) - 在顶部删除
  • 访问栈顶(top):O(1) - 直接访问
  • 不支持随机访问和遍历

空间复杂度: O(n),取决于底层容器

初始化

stack<int> s1;                        // 空栈(默认使用 deque)
stack<int, vector<int>> s2;          // 使用 vector 作为底层容器
stack<int, list<int>> s3;            // 使用 list 作为底层容器

stack<int> s;
s.push(1);                           // 入栈: {1}
s.push(2);                           // 入栈: {1, 2}
s.push(3);                           // 入栈: {1, 2, 3}
s.emplace(4);                        // 原地构造并入栈: {1, 2, 3, 4}

stack<int> s;
s.push(1); s.push(2); s.push(3);
s.pop();                             // 出栈(删除栈顶元素): {1, 2}
s.pop();                             // 出栈: {1}
// 注意:pop() 不返回值,需要先用 top() 获取值再 pop()

stack<int> s;
s.push(1); s.push(2); s.push(3);
int top_val = s.top();               // 获取栈顶元素: 3
size_t size = s.size();              // 获取元素个数: 3
bool empty = s.empty();              // 检查是否为空: false

priority_queue

priority_queue 是优先级队列,基于堆实现,元素按优先级排序,每次弹出的是优先级最高(默认最大)的元素。

容器适配器特性:

priority_queue 默认使用 vector 作为底层容器,因为堆操作需要随机访问能力。也可以使用 deque,但不能使用 list(list 不支持随机访问)。

// 默认使用 vector
priority_queue<int> pq1;
// 等价于
priority_queue<int, vector<int>, less<int>> pq1;

// 也可以使用 deque
priority_queue<int, deque<int>, less<int>> pq2;

内存布局: 基于 vector 实现的最大堆(max heap)。内部使用 vector 存储元素,通过堆算法维护堆性质:父节点的值总是大于等于子节点的值。

priority_queue 对象: [底层 vector 对象]
                     [比较函数对象]

底层 vector(最大堆):
      [9]
     /   \
   [7]   [5]
   / \   / \
 [3][4][1][2]

存储为: [9, 7, 5, 3, 4, 1, 2]

时间复杂度:

  • 入队(push):O(log n) - 插入新元素后需要上浮(sift up)调整堆
  • 出队(pop):O(log n) - 删除堆顶后需要下沉(sift down)调整堆
  • 访问堆顶(top):O(1) - 直接访问 vector 首元素
  • 构造堆(从容器构造):O(n) - 使用 heapify 算法
  • 不支持遍历和查找

空间复杂度: O(n),底层 vector 存储所有元素

初始化

priority_queue<int> pq1;                                    // 默认最大堆
priority_queue<int, vector<int>, greater<int>> pq2;        // 最小堆
priority_queue<int, vector<int>, less<int>> pq3;           // 最大堆(显式指定)

// 从容器构造
vector<int> data = {3, 1, 4, 1, 5, 9, 2, 6};
priority_queue<int> pq4(data.begin(), data.end());

// 自定义比较函数
auto cmp = [](int a, int b) { return a > b; };  // 最小堆
priority_queue<int, vector<int>, decltype(cmp)> pq5(cmp);

priority_queue<int> pq;
pq.push(3);                          // 入队: {3}
pq.push(1);                          // 入队: {3, 1}
pq.push(4);                          // 入队: {4, 3, 1}
pq.push(2);                          // 入队: {4, 3, 1, 2}
pq.emplace(5);                       // 原地构造并入队: {5, 4, 3, 1, 2}

priority_queue<int> pq;
pq.push(3); pq.push(1); pq.push(4);
int top_val = pq.top();              // 获取堆顶元素: 4
pq.pop();                            // 删除堆顶元素: {3, 1}
// 注意:pop() 不返回值,需要先用 top() 获取值再 pop()

priority_queue<int> pq;
pq.push(3); pq.push(1); pq.push(4);
int top_val = pq.top();              // 获取堆顶元素(最大值): 4
size_t size = pq.size();             // 获取元素个数: 3
bool empty = pq.empty();             // 检查是否为空: false

四、视图与工具类(Views & Utilities)

视图和工具类不直接拥有数据,提供轻量级的访问和操作能力。

span

span 是 C++20 引入的轻量级视图,提供对连续序列(array、vector、string、指针)的非拥有型引用。不拥有数据,不能增加删除数据。

内存布局: 仅存储指针和大小。span 对象只包含两个成员:指向数据起始位置的指针和元素数量,不拷贝底层数据。

原始数据(vector/array/etc): [元素0][元素1][元素2][元素3][元素4]span 对象: [指针] --------------[大小: 5]

sizeof(span<int>) ≈ 16 字节(指针8字节 + size_t 8字节)

时间复杂度:

  • 创建 span:O(1) - 只存储指针和大小,不拷贝数据
  • 随机访问([]):O(1) - 直接通过指针偏移
  • 子视图(subspan/first/last):O(1) - 创建新的 span 对象,指向原数据的子区间
  • 不支持插入和删除操作

空间复杂度: O(1),只存储指针和大小,不拷贝底层数据

初始化

vector<int> vec = {1, 2, 3, 4, 5};

span<int> s1{vec};                    // 原来数据创建
span<int> s2{vec.begin(), 2}; // constexpr span( It first, size_type count );
span<int> s2{vec.begin(), vec.end()}; // constexpr span( It first, End last );

// 不可变的 vec
const vector<int> vec_const = {1, 2, 3, 4, 5};
span<const int> s3{vec_const};

span<int> s = {arr, 5};
int first = s[0];                     // 访问元素
int first_alt = s.front();            // 访问第一个元素
int last = s.back();                  // 访问最后一个元素
size_t size = s.size();               // 获取元素个数
bool empty = s.empty();               // 检查是否为空
int* data = s.data();                 // 获取底层数据指针

// 子视图操作
span<int> first_half = s.first(3);    // 前3个元素
span<int> last_half = s.last(2);      // 后2个元素
span<int> sub = s.subspan(1, 3);      // 从索引1开始的3个元素

span<int> s = {arr, 5};
s[2] = 10;                            // 修改原始数据

optional

optional 是 C++17 引入的模板类,用于表示一个可能存在也可能不存在的值,提供了一种类型安全的方式来处理可选值。

内存布局: 存储值 + 布尔标志。optional 对象包含实际值的存储空间和一个布尔标志位,指示值是否存在。即使为空也会占用完整的值空间。

optional<int> 对象:
  有值时: [标志: true][值: 42]
  无值时: [标志: false][未初始化的存储空间]

sizeof(optional<T>) ≈ sizeof(T) + sizeof(bool) + 对齐填充

时间复杂度:

  • 赋值/构造:O(1) 或 O(T的构造成本) - 取决于类型 T
  • 访问(value/value_or/*):O(1) - 直接访问存储的值
  • 检查(has_value):O(1) - 检查布尔标志
  • 重置(reset):O(1) 或 O(T的析构成本) - 调用析构函数并设置标志为 false

空间复杂度: O(1),固定大小,约等于 sizeof(T) + 1 字节(加对齐)

初始化

optional<int> o1;                    // 空 optional
optional<int> o2 = 42;              // 包含值 42
optional<int> o3 = nullopt;         // 显式设置为空
optional<int> o4 = make_optional(42); // 使用 make_optional 创建

增/改

optional<int> o;
o = 42;                             // 赋值
o.emplace(42);                      // 原地构造
o = nullopt;                        // 设置为空

optional<int> o = 42;
if (o.has_value()) {               // 检查是否有值
    cout << "有值" << endl;
}
if (o) {                           // 也可以直接用作布尔值
    cout << "有值" << endl;
}

int value = o.value();             // 获取值(如果为空则抛出异常)
int value2 = o.value_or(0);        // 获取值,如果为空则返回默认值
int value3 = *o;                   // 使用解引用操作符获取值

// 使用 if-let 风格的语法(C++17)
if (auto value = o) {
    cout << *value << endl;
}

其他操作

optional<int> o = 42;
o.reset();                         // 重置为空
o.swap(o2);                        // 交换两个 optional 的值

链式调用

optional<int> o1 = 42;
optional<int> o2 = nullopt;

// or_else: 当 optional 为空时执行一个函数
auto result1 = o1.or_else([] { return optional<int>(0); });  // 返回 optional<int>(84)
auto result2 = o2.or_else([] { return optional<int>(0); });  // 返回 optional<int>(0)

// transform: 当 optional 有值时,对其值进行转换
auto result3 = o1.transform([](int x) { return x * 2; });    // 返回 optional<int>(84)
auto result4 = o2.transform([](int x) { return x * 2; });    // 返回 nullopt

// and_then: 当 optional 有值时,执行一个返回 optional 的函数
auto result5 = o1.and_then([](int x) { 
    return x > 0 ? optional<int>(x * 2) : nullopt; 
});  // 返回 optional<int>(84)

auto result6 = o2.and_then([](int x) { 
    return x > 0 ? optional<int>(x * 2) : nullopt; 
});  // 返回 nullopt

// 链式调用示例
optional<int> o3 = 10;
auto result7 = o3
    .transform([](int x) { return x * 2; })      // 20
    .and_then([](int x) { 
        return x > 15 ? optional<int>(x) : nullopt; 
    })                                           // optional<int>(20)
    .or_else([] { return optional<int>(0); });   // optional<int>(20)

实现伪代码

template <typename F>
auto transform(F&& f) const {
    if (this->has_value()) {
        // f 返回普通值,外层包成 optional
        return std::optional{f(this->value_)};
    } else {
        return std::nullopt;
    }
}

template <typename F>
auto and_then(F&& f) const {
    if (this->has_value()) {
        // f 必须返回 optional
        return f(this->value_);
    } else {
        return std::nullopt;
    }
}

std::ranges

std::ranges 是 C++20 引入的新特性,提供了一种更现代、更安全的方式来处理容器和迭代器,引入了概念(concepts)、视图(views)和范围算法(range algorithms)。

核心思想: 惰性求值(Lazy Evaluation)+ 可组合性。视图(views)不会立即执行操作或拷贝数据,只在真正需要结果时才进行计算,并且可以通过管道操作符(|)链式组合。

时间复杂度:

  • 创建视图(views):O(1) - 惰性求值,不立即执行
  • 迭代视图:取决于底层操作和数据量
    • filter:O(n) - 需要检查每个元素
    • transform:O(n) - 需要转换每个元素
    • take(k):O(k) - 只处理前 k 个元素
  • 范围算法:与传统 STL 算法相同,但接受范围而非迭代器对

空间复杂度:

  • 视图:O(1) - 不拷贝底层数据,只存储必要的状态(如迭代器、谓词函数等)
  • 转换为容器(ranges::to):O(n) - 需要分配新容器并拷贝数据
concepts
#include <ranges>
#include <vector>
#include <iostream>

// 范围概念
std::ranges::range auto r = std::vector{1, 2, 3, 4, 5};

// 输入范围
std::ranges::input_range auto input_r = std::vector{1, 2, 3};

// 前向范围
std::ranges::forward_range auto forward_r = std::vector{1, 2, 3};

// 双向范围
std::ranges::bidirectional_range auto bidir_r = std::vector{1, 2, 3};

// 随机访问范围
std::ranges::random_access_range auto random_r = std::vector{1, 2, 3};

// 连续范围
std::ranges::contiguous_range auto contig_r = std::vector{1, 2, 3};
views

视图是惰性求值的范围适配器,不会复制数据。

#include <ranges>
#include <vector>
#include <iostream>

std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// 过滤视图
auto even_numbers = numbers | std::views::filter([](int n) { return n % 2 == 0; });
// 结果: {2, 4, 6, 8, 10}

// 转换视图
auto doubled = numbers | std::views::transform([](int n) { return n * 2; });
// 结果: {2, 4, 6, 8, 10, 12, 14, 16, 18, 20}

// 取前N个元素
auto first_three = numbers | std::views::take(3);
// 结果: {1, 2, 3}

// 跳过前N个元素
auto skip_first_three = numbers | std::views::drop(3);
// 结果: {4, 5, 6, 7, 8, 9, 10}

// 反转视图
auto reversed = numbers | std::views::reverse;
// 结果: {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}

// 链式视图操作
auto result = numbers 
    | std::views::filter([](int n) { return n % 2 == 0; })  // 过滤偶数
    | std::views::transform([](int n) { return n * n; })    // 平方
    | std::views::take(3);                                  // 取前3个
// 结果: {4, 16, 36}

// 打印结果
for (int n : result) {
    std::cout << n << " ";
}
std::cout << std::endl;

惰性求值(Lazy Evaluation)的含义:

惰性求值是指视图不会立即执行操作,而是等到真正需要数据时才进行计算。

#include <ranges>
#include <vector>
#include <iostream>

std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// 创建视图时不会执行任何计算
auto view = numbers | std::views::filter([](int x) { 
    std::cout << "检查元素: " << x << std::endl;  // 这行代码此时不会执行
    return x % 2 == 0; 
}) | std::views::transform([](int x) {
    std::cout << "转换元素: " << x << std::endl;  // 这行代码此时不会执行
    return x * 2;
});

std::cout << "视图已创建,但还没有执行任何计算" << std::endl;

// 只有在迭代时才会执行计算
for (int n : view) {
    std::cout << "得到结果: " << n << std::endl;
}
// 输出顺序:
// 检查元素: 1
// 检查元素: 2
// 转换元素: 2
// 得到结果: 4
// 检查元素: 3
// 检查元素: 4
// 转换元素: 4
// 得到结果: 8
// ...以此类推

惰性求值的优势:

// 1. 内存效率 - 不需要存储中间结果
std::vector<int> large_data(1000000);
auto processed = large_data 
    | std::views::filter([](int x) { return x % 2 == 0; })     // 不复制数据
    | std::views::transform([](int x) { return x * 2; })       // 不复制数据
    | std::views::take(10);                                    // 只取前10个

// 2. 可以处理无限序列
auto infinite = std::views::iota(1);  // 无限序列 1, 2, 3, 4, ...
auto first_ten = infinite | std::views::take(10);  // 只取前10个

// 3. 避免不必要的计算
auto expensive_view = large_data 
    | std::views::filter([](int x) { 
        std::cout << "执行昂贵的过滤操作" << std::endl;
        return x > 500000; 
    })
    | std::views::take(5);  // 只计算前5个满足条件的元素

// 4. 支持短路求值
auto short_circuit = large_data 
    | std::views::filter([](int x) { return x > 0; })
    | std::views::take(1);  // 找到第一个就停止
algorithms
#include <ranges>
#include <vector>
#include <algorithm>
#include <iostream>

std::vector<int> numbers = {3, 1, 4, 1, 5, 9, 2, 6};

// 查找算法
auto it1 = std::ranges::find(numbers, 5);  // 查找元素
auto it2 = std::ranges::find_if(numbers, [](int n) { return n > 5; });  // 条件查找

// 计数算法
int count = std::ranges::count(numbers, 1);  // 计数特定元素
int count_if = std::ranges::count_if(numbers, [](int n) { return n % 2 == 0; });  // 条件计数

// 排序算法
std::ranges::sort(numbers);  // 排序
std::ranges::sort(numbers, std::greater{});  // 降序排序
std::ranges::partial_sort(numbers, numbers.begin() + 3);  // 部分排序

// 复制算法
std::vector<int> dest(8);
std::ranges::copy(numbers, dest.begin());  // 复制到目标容器

// 转换算法
std::vector<int> doubled;
std::ranges::transform(numbers, std::back_inserter(doubled), [](int n) { return n * 2; });

// 填充算法
std::ranges::fill(numbers, 0);  // 填充为0
std::ranges::generate(numbers, []() { return rand() % 100; });  // 生成随机数

// 移除算法
auto new_end = std::ranges::remove(numbers, 1);  // 移除特定元素
auto new_end2 = std::ranges::remove_if(numbers, [](int n) { return n < 3; });  // 条件移除

// 唯一化算法
auto unique_end = std::ranges::unique(numbers);  // 移除连续重复元素

// 替换算法
std::ranges::replace(numbers, 1, 10);  // 替换特定元素
std::ranges::replace_if(numbers, [](int n) { return n < 3; }, 0);  // 条件替换

// 旋转算法
std::ranges::rotate(numbers, numbers.begin() + 3);  // 旋转

// 洗牌算法
std::ranges::shuffle(numbers, std::mt19937{std::random_device{}}());  // 随机洗牌
工具函数
#include <ranges>
#include <vector>
#include <iostream>

std::vector<int> numbers = {1, 2, 3, 4, 5};

// 距离计算
auto distance = std::ranges::distance(numbers.begin(), numbers.end());

// 下一个迭代器
auto next_it = std::ranges::next(numbers.begin(), 2);

// 前一个迭代器
auto prev_it = std::ranges::prev(numbers.end(), 2);

// 交换元素
std::ranges::swap(numbers[0], numbers[1]);

// 迭代器对
auto [begin, end] = std::ranges::subrange(numbers);

// 范围大小
auto size = std::ranges::size(numbers);

// 范围是否为空
bool empty = std::ranges::empty(numbers);

// 范围数据指针
auto data = std::ranges::data(numbers);

std::ranges::to - 范围转换工具:

std::ranges::to 是 C++23 引入的工具函数,用于将范围(range)转换为指定的容器类型。它提供了一种简洁、类型安全的方式来将视图或其他范围转换为具体的容器。

#include <ranges>
#include <vector>
#include <list>
#include <set>
#include <string>
#include <iostream>

std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// 基本用法 - 将视图转换为 vector
auto even_numbers = numbers 
    | std::views::filter([](int x) { return x % 2 == 0; })
    | std::ranges::to<std::vector<int>>();  // 转换为 vector<int>

// 转换为不同类型的容器
auto doubled_list = numbers 
    | std::views::transform([](int x) { return x * 2; })
    | std::ranges::to<std::list<int>>();  // 转换为 list<int>

auto unique_set = numbers 
    | std::views::filter([](int x) { return x > 5; })
    | std::ranges::to<std::set<int>>();  // 转换为 set<int>

// 字符串处理
std::string text = "hello world";
auto words = text 
    | std::views::split(' ')
    | std::ranges::to<std::vector<std::string>>();

// 嵌套容器
auto matrix = std::views::iota(1, 4)
    | std::views::transform([](int i) {
        return std::views::iota(1, i + 1) | std::ranges::to<std::vector<int>>();
    })
    | std::ranges::to<std::vector<std::vector<int>>>();