简易STL实现 | Multiset 的实现

multiset 是一个有序容器,允许存储多个相同的元素,并按照元素的值进行排序。multiset 的内部通常 基于红黑树实现

1、multiset 的特性

1、multiset 中的元素是有序的,很方便地进行有序访问

2、存储相同值的多个元素

3、基于红黑树实现。红黑树是一种 自平衡的二叉搜索树,确保了 插入、删除和查找等 操作的高效性能

2、multiset 的性能考虑

1、multiset 内部使用红黑树实现,插入和删除元素 平均情况下 时间复杂度为 O(log n),红黑树的有序性 使得查找操作 也具有很好的性能,平均情况下 时间复杂度为 O(log n)

2、multiset 的内部实现 通常需要 额外的内存 来存储红黑树的节点,因此在内存开销上 相对于一些其他容器 可能会更高一些

3、标准库中 multiset 的基本用法

#include <set>

std::multiset<int> myMultiset;
myMultiset.insert(42);

for (const auto& elem : myMultiset) {
	std::cout << elem << " ";
}

auto it = myMultiset.find(56);
if (it != myMultiset.end()) {
	std::cout << "Element 56 found in the multiset." << std::endl;
} else {
	std::cout << "Element 56 not found in the multiset." << std::endl;
}

4、multiset 工作原理

1、为了存储重复元素,红黑树的每个节点 都包含 一个键和一个计数器,用于 表示该键在 multiset 中的出现次数

2、在插入元素时,如果 键已经存在于红黑树中,则将计数器加 1;如果键 不存在于红黑树中,则插入一个新的节点,并将计数器初始化为 1

3、在删除元素时,如果计数器大于 1,则将计数器减 1;如果计数器 等于 1,则从红黑树中删除该节点(不管出现多少次,都删除干净)

4、在查找元素时,红黑树的查找操作 可以找到 包含该键的节点,并返回 该节点的计数器值

5、实现 multiset

<cstddef> 包含以下内容:

类型定义:
size_t:一种无符号整数类型,通常用于 表示对象的大小或数组的索引
ptrdiff_t:一种有符号整数类型,用于 表示两个指针之间的差值
nullptr_t(自 C++11 起):表示 指针空值 nullptr 的类型

宏定义:
offsetof(type, member):用于计算结构体或类中成员相对于其起始地址的偏移量

其他内容:
NULL:表示空指针常量。但在现代 C++ 中,建议使用 nullptr 代替 NULL

#include <iostream>
#include <sstream>
#include <string>
#include <cstddef>

enum class Color { RED, BLACK };

template <typename Key, typename Value> class RedBlackTree {
    class Node {
    public:
        Key key;
        Value value;
        Color color;
        Node* left;
        Node* right;
        Node* parent;

        // 构造函数
        Node(const Key& k, const Value& v, Color c, Node* p = nullptr)
            : key(k), value(v), color(c), left(nullptr), right(nullptr), parent(p) {
        }
        Node()
            : color(Color::BLACK), left(nullptr), right(nullptr), parent(nullptr) {}
    };

private:
    Node* root;
    size_t size;
    Node* Nil;

    // 查询某节点
    Node* lookUp(Key key) {
        Node* cmpNode = root;

        while (cmpNode) {
            if (key < cmpNode->key) {
                cmpNode = cmpNode->left;
            }
            else if (key > cmpNode->key) {
                cmpNode = cmpNode->right;
            }
            else {
                return cmpNode;
            }
        }
        return cmpNode;
    }

    // 右旋函数
    void rightRotate(Node* node) {
        Node* l_son = node->left; // 获取当前节点的左子节点

        // 当前节点的左子树变成左子节点的右子树
        node->left = l_son->right;
        // 如果左子节点的右子树非空,更新其父指针
        if (l_son->right) {
            l_son->right->parent = node;
        }

        // 左子节点升为当前节点位置,并处理父节点关系
        l_son->parent = node->parent;
        // 如果当前节点是根节点,更新根节点为左子节点
        if (!node->parent) {
            root = l_son;
            // 如果当前节点是其父节点的左子节点,更新父节点的左子节点为左子节点
        }
        else if (node == node->parent->left) {
            node->parent->left = l_son;
            // 如果当前节点是其父节点的右子节点,更新父节点的右子节点为左子节点
        }
        else {
            node->parent->right = l_son;
        }

        // 完成右旋转,将当前节点成为左子节点的右子节点
        l_son->right = node;
        // 更新当前节点的父节点为左子节点
        node->parent = l_son;
    }

    // 左旋
    // 是右旋的对称情况, 逻辑和右旋是一样的
    void leftRotate(Node* node) {
        Node* r_son = node->right;

        node->right = r_son->left;

        if (r_son->left) {
            r_son->left->parent = node;
        }

        r_son->parent = node->parent;
        if (!node->parent) {
            root = r_son;
        }
        else if (node == node->parent->left) {
            node->parent->left = r_son;
        }
        else {
            node->parent->right = r_son;
        }

        r_son->left = node;
        node->parent = r_son;
    }

    // 插入修复函数
    void insertFixup(Node* target) {
        // 当目标节点的父节点存在且父节点的颜色是红色时,需要修复
        while (target->parent && target->parent->color == Color::RED) {
            // 当目标节点的父节点是祖父节点的左子节点时
            if (target->parent == target->parent->parent->left) {
                Node* uncle = target->parent->parent->right; // 叔叔节点
                // 如果叔叔节点存在且为红色,进行颜色调整
                if (uncle && uncle->color == Color::RED) {
                    target->parent->color = Color::BLACK; // 父节点设为黑色
                    uncle->color = Color::BLACK;          // 叔叔节点设为黑色
                    target->parent->parent->color = Color::RED; // 祖父节点设为红色
                    target = target->parent->parent; // 将祖父节点设为下一个目标节点
                }
                else {
                    // 如果目标节点是父节点的右子节点,进行左旋转
                    if (target == target->parent->right) {
                        target = target->parent; // 更新目标节点为父节点
                        leftRotate(target);      // 对目标节点进行左旋
                    }
                    // 调整父节点和祖父节点的颜色,并进行右旋转
                    target->parent->color = Color::BLACK;
                    target->parent->parent->color = Color::RED;
                    rightRotate(target->parent->parent);
                }
            }
            else {
                // 当目标节点的父节点是祖父节点的右子节点时,与上面对称
                Node* uncle = target->parent->parent->left; // 叔叔节点
                if (uncle && uncle->color == Color::RED) {
                    target->parent->color = Color::BLACK;
                    uncle->color = Color::BLACK;
                    target->parent->parent->color = Color::RED;
                    target = target->parent->parent;
                }
                else {
                    if (target == target->parent->left) {
                        target = target->parent; // 更新目标节点为父节点
                        rightRotate(target);     // 对目标节点进行右旋
                    }
                    // 调整父节点和祖父节点的颜色,并进行左旋转
                    target->parent->color = Color::BLACK;
                    target->parent->parent->color = Color::RED;
                    leftRotate(target->parent->parent);
                }
            }
        }
        // 确保根节点始终为黑色
        root->color = Color::BLACK;
    }

    // 插入节点函数
    void insertNode(const Key& key, const Value& value) {
        // 创建一个新节点,节点的颜色初始化为红色
        Node* newNode = new Node(key, value, Color::RED);
        Node* parent = nullptr; // 新节点的父节点指针
        Node* cmpNode = root;   // 用于比较的节点,初始为根节点

        // 遍历树,找到新节点的正确位置
        while (cmpNode) {
            parent = cmpNode; // 保留当前节点作为新节点的潜在父节点
            // 如果新节点的键小于当前比较节点的键,则向左子树移动
            if (newNode->key < cmpNode->key) {
                cmpNode = cmpNode->left;
                // 如果新节点的键大于当前比较节点的键,则向右子树移动
            }
            else if (newNode->key > cmpNode->key) {
                cmpNode = cmpNode->right;
                // 如果键相等,则说明树中已有相同键的节点,删除新节点并返回
            }
            else {
                delete newNode;
                return;
            }
        }

        // 树的大小增加
        size++;

        // 将新节点的父节点设置为找到的父节点位置
        newNode->parent = parent;
        // 如果父节点为空,说明树是空的,新节点成为根节点
        if (!parent) {
            root = newNode;
            // 如果新节点的键小于父节点的键,将新节点插入父节点的左子树
        }
        else if (newNode->key < parent->key) {
            parent->left = newNode;
            // 否则,将新节点插入父节点的右子树
        }
        else {
            parent->right = newNode;
        }

        // 插入新节点后,调用insertFixup函数来修复可能破坏的红黑树性质
        insertFixup(newNode);
    }

    // 中序遍历
    void inorderTraversal(Node* node) const {
        if (node) {
            inorderTraversal(node->left);
            std::cout << node->key << " ";
            std::cout << node->value << " ";
            inorderTraversal(node->right);
        }
    }
    // 辅助函数,用新节点替换旧节点
    void replaceNode(Node* targetNode, Node* newNode) {
        if (!targetNode->parent) {
            root = newNode;
        }
        else if (targetNode == targetNode->parent->left) {
            targetNode->parent->left = newNode;
        }
        else {
            targetNode->parent->right = newNode;
        }
        if (newNode) {
            newNode->parent = targetNode->parent;
        }
    }

    // 寻找以某个节点为根节点的子树中的最小节点
    Node* findMinimumNode(Node* node) {
        while (node->left) {
            node = node->left;
        }
        return node;
    }

    // removeFixup函数用于在删除节点后恢复红黑树的性质
    void removeFixup(Node* node) {
        // 如果节点为Nil并且没有父节点,说明它是唯一的节点,直接返回
        if (node == Nil && node->parent == nullptr) {
            return;
        }

        // 当我们没有到达根节点时继续循环
        while (node != root) {
            // 如果节点是其父节点的左子节点
            if (node == node->parent->left) {
                // 兄弟节点是节点父亲的右子节点
                Node* sibling = node->parent->right;

                // 情况1:节点的兄弟节点是红色
                if (getColor(sibling) == Color::RED) {
                    // 重新着色兄弟节点和父节点,并进行左旋
                    setColor(sibling, Color::BLACK);
                    setColor(node->parent, Color::RED);
                    leftRotate(node->parent);
                    // 旋转后更新兄弟节点
                    sibling = node->parent->right;
                }

                // 情况2:兄弟节点的两个子节点都是黑色
                if (getColor(sibling->left) == Color::BLACK &&
                    getColor(sibling->right) == Color::BLACK) {
                    // 重新着色兄弟节点并向上移动
                    setColor(sibling, Color::RED);
                    node = node->parent;
                    // 如果父节点是红色,将其改为黑色并结束
                    if (node->color == Color::RED) {
                        node->color = Color::BLACK;
                        node = root;
                    }
                }
                else {
                    // 情况3:兄弟节点的右子节点是黑色(左子节点是红色)
                    if (getColor(sibling->right) == Color::BLACK) {
                        // 重新着色兄弟节点和兄弟节点的左子节点,并进行右旋
                        setColor(sibling->left, Color::BLACK);
                        setColor(sibling, Color::RED);
                        rightRotate(sibling);
                        // 旋转后更新兄弟节点
                        sibling = node->parent->right;
                    }

                    // 情况4:兄弟节点的右子节点是红色
                    setColor(sibling, getColor(node->parent));
                    setColor(node->parent, Color::BLACK);
                    setColor(sibling->right, Color::BLACK);
                    leftRotate(node->parent);
                    // 移动到根节点结束
                    node = root;
                }
            }
            else {
                // 当节点是其父节点的右子节点时,对称的情况
                Node* sibling = node->parent->left;

                if (getColor(sibling) == Color::RED) {
                    setColor(sibling, Color::BLACK);
                    setColor(node->parent, Color::RED);
                    rightRotate(node->parent);
                    sibling = node->parent->left;
                }

                if (getColor(sibling->right) == Color::BLACK &&
                    getColor(sibling->left) == Color::BLACK) {
                    setColor(sibling, Color::RED);
                    node = node->parent;
                    if (node->color == Color::RED) {
                        node->color = Color::BLACK;
                        node = root;
                    }
                }
                else {
                    if (getColor(sibling->left) == Color::BLACK) {
                        setColor(sibling->right, Color::BLACK);
                        setColor(sibling, Color::RED);
                        leftRotate(sibling);
                        sibling = node->parent->left;
                    }
                    setColor(sibling, getColor(node->parent));
                    setColor(node->parent, Color::BLACK);
                    setColor(sibling->left, Color::BLACK);
                    rightRotate(node->parent);
                    node = root;
                }
            }
        }
        // 确保当前节点是黑色的,以维持红黑树性质
        setColor(node, Color::BLACK);
    }

    // 获取颜色, 空指针为黑色
    Color getColor(Node* node) {
        if (node == nullptr) {
            return Color::BLACK;
        }
        return node->color;
    }

    void setColor(Node* node, Color color) {
        if (node == nullptr) {
            return;
        }
        node->color = color;
    }

    // 取消Nil哨兵的连接
    void dieConnectNil() {
        if (Nil == nullptr) {
            return;
        }
        if (Nil->parent != nullptr) {
            if (Nil == Nil->parent->left) {
                Nil->parent->left = nullptr;
            }
            else {
                Nil->parent->right = nullptr;
            }
        }
    }

    // 删除节点
    void deleteNode(Node* del) {
        Node* rep = del; // rep(替代节点)初始指向要删除的节点
        Node* child = nullptr;      // 要删除节点的孩子节点
        Node* parentRP;             // 替代节点的父节点
        Color origCol = rep->color; // 保存要删除节点的原始颜色

        // 如果删除节点没有左孩子
        if (!del->left) {
            rep = del->right;        // 替代节点指向删除节点的右孩子
            parentRP = del->parent;  // 更新替代节点的父节点
            origCol = getColor(rep); // 获取替代节点的颜色
            replaceNode(del, rep);   // 用替代节点替换删除节点
        }
        // 如果删除节点没有右孩子
        else if (!del->right) {
            rep = del->left;         // 替代节点指向删除节点的左孩子
            parentRP = del->parent;  // 更新替代节点的父节点
            origCol = getColor(rep); // 获取替代节点的颜色
            replaceNode(del, rep);   // 用替代节点替换删除节点
        }
        // 如果删除节点有两个孩子
        else {
            rep = findMinimumNode(
                del->right); // 找到删除节点右子树中的最小节点作为替代节点
            origCol = rep->color; // 保存替代节点的原始颜色
            // 如果替代节点不是删除节点的直接右孩子
            if (rep != del->right) {
                parentRP = rep->parent; // 更新替代节点的父节点
                child = rep->right; // 替代节点的右孩子变成要处理的孩子节点
                parentRP->left =
                    child; // 替代节点的父节点的左孩子指向替代节点的孩子(因为替代节点是最小节点,所以不可能有左孩子)
                if (child != nullptr) {
                    child->parent = parentRP; // 如果替代节点的孩子存在,则更新其父节点
                }
                // 将替代节点放到删除节点的位置
                del->left->parent = rep;
                del->right->parent = rep;
                rep->left = del->left;
                rep->right = del->right;
                // 如果删除节点有父节点,更新父节点的孩子指向
                if (del->parent != nullptr) {
                    if (del == del->parent->left) {
                        del->parent->left = rep;
                        rep->parent = del->parent;
                    }
                    else {
                        del->parent->right = rep;
                        rep->parent = del->parent;
                    }
                }
                // 如果删除节点没有父节点,说明它是根节点
                else {
                    root = rep;
                    root->parent = nullptr;
                }
            }
            // 如果替代节点是删除节点的直接右孩子
            else {
                child = rep->right; // 孩子节点指向替代节点的右孩子
                rep->left = del->left; // 替代节点的左孩子指向删除节点的左孩子
                del->left->parent = rep; // 更新左孩子的父节点
                // 更新删除节点父节点的孩子指向
                if (del->parent != nullptr) {
                    if (del == del->parent->left) {
                        del->parent->left = rep;
                        rep->parent = del->parent;
                    }
                    else {
                        del->parent->right = rep;
                        rep->parent = del->parent;
                    }
                }
                // 如果删除节点是根节点
                else {
                    root = rep;
                    root->parent = nullptr;
                }
                parentRP = rep; // 更新替代节点的父节点
            }
        }

        // 如果替代节点存在,更新其颜色为删除节点的颜色
        if (rep != nullptr) {
            rep->color = del->color;
        }
        // 如果替代节点不存在,将删除节点的颜色赋给origCol变量
        else {
            origCol = del->color;
        }

        // 如果原始颜色是黑色,需要进行额外的修复操作,因为黑色节点的删除可能会破坏红黑树的性质
        if (origCol == Color::BLACK) {
            // 如果存在孩子节点,进行修复操作
            if (child != nullptr) {
                removeFixup(child);
            }
            // 如果不存在孩子节点,将Nil节点(代表空节点)的父节点设置为替代节点的父节点
            else {
                Nil->parent = parentRP;
                // 如果替代节点的父节点存在,设置其对应的孩子指针为Nil节点
                if (parentRP != nullptr) {
                    if (parentRP->left == nullptr) {
                        parentRP->left = Nil;
                    }
                    else {
                        parentRP->right = Nil;
                    }
                }
                // 进行修复操作
                removeFixup(Nil);
                // 断开Nil节点与树的连接,因为在红黑树中Nil节点通常是单独存在的
                dieConnectNil();
            }
        }

        // 删除节点
        delete del;
    }

public:
    // 构造函数
    RedBlackTree() : root(nullptr), size(0), Nil(new Node()) {
        Nil->color = Color::BLACK;
    }

    // 插入
    void insert(const Key& key, const Value& value) { insertNode(key, value); }

    // 删除
    void remove(const Key& key) {
        Node* nodeToBeRemoved = lookUp(key);
        if (nodeToBeRemoved != nullptr) {
            deleteNode(nodeToBeRemoved);
            size--;
        }
    }

    Value* at(const Key& key) {
        auto ans = lookUp(key);
        if (ans != nullptr) {
            return &ans->value;
        }
        return nullptr;
    }

    int getSize() { return size; }

    bool empty() { return size == 0; }

    // 中序遍历打印
    void print() {
        inorderTraversal(root);
        std::cout << std::endl;
    }

    void clear() {
        deleteNode(root);
        size = 0;
    }

    // 析构函数
    ~RedBlackTree() {
        // 释放节点内存
        deleteTree(root);
    }

private:
    // 递归释放节点内存
    void deleteTree(Node* node) {
        if (node) {
            deleteTree(node->left);
            deleteTree(node->right);
            delete node;
        }
    }
};
// 此处开始 Multiset
template <typename Key>
class Multiset {
    RedBlackTree<Key, size_t> rbTree;
    size_t size;

public:
    Multiset() : size(0) {} // rbTree 使用默认构造函数
    ~Multiset() {} //  自动调用 rbTree 析构函数

    void insert(const Key& key) {
        auto num = rbTree.at(key); // 返回的是指针,让其可更改
        if (num == nullptr) {
            rbTree.insert(key, 1);
            size++;
        }
        else {
            (*num)++;
            size++;
        }
    }

    void erase(const Key& key) {
        auto num = rbTree.at(key);
        if (num == nullptr) {
            return;
        }
        size -= (*num); // 必须在remove之前
        rbTree.remove(key); // 要删全部都删
        
    }

    size_t getSize() const { return size; }

    bool empty() const { return size == 0; }

    // 返回给定key的值的数量
    size_t count(const Key& key) {
        auto num = rbTree.at(key);
        if (num == nullptr) {
            return 0;
        }
        return *num;
    }

    void clear() {
        size = 0;
        rbTree.clear();
    }

};

int main()
{
    int N;
    std::cin >> N;
    getchar();
    Multiset<int> mset;
    while (N--) {
        std::string line;
        std::getline(std::cin, line);
        std::istringstream iss(line);
        std::string command;
        iss >> command;
        if (command == "insert") {
            int num; // 自动转成 int?
            iss >> num;
            mset.insert(num);
        }
        if (command == "erase") {
            int num;
            iss >> num;
            mset.erase(num);
        }
        if (command == "count") {
            int num;
            iss >> num;
            std::cout << mset.count(num) << std::endl;
        }
        if (command == "empty") {
            if (mset.empty()) {
                std::cout << "true" << std::endl;;
            }
            else {
                std::cout << "false" << std::endl;
            }
        }
        if (command == "size") {
            std::cout << mset.getSize() << std::endl;
        }
    }
    return 0;
}

std::istringstream 是 C++ 标准库 <sstream> 中的一个类,用于 将字符串转换为输入流,从而方便地 从字符串中提取格式化数据

主要功能
字符串输入流:将一个字符串 封装为 输入流,支持标准的流操作
数据解析:方便地 从字符串中 提取不同类型的数据,如整数、浮点数、字符串等(会自动转化)

std::string data = "100 3.14 hello";
std::istringstream iss(data);

int num;
double pi;
std::string text;

iss >> num >> pi >> text;

std::cout << "整数: " << num << std::endl;
std::cout << "浮点数: " << pi << std::endl;
std::cout << "字符串: " << text << std::endl;

输出:

整数: 100
浮点数: 3.14
字符串: hello
void erase(const Key& key) {
    auto num = rbTree.at(key);
    if (num == nullptr) {
        return;
    }
    size -= (*num); // 必须在remove之前
    rbTree.remove(key); // 要删全部都删
}

size -= (*num); 必须在 rbTree.remove(key); 之前执行的原因是,一旦调用了 rbTree.remove(key);,与 key 关联的节点 将从红黑树中被删除,num 指针 将指向 已被释放的内存空间。此时,再访问 *num 会导致未定义的行为,可能会 引发程序崩溃或其他错误

6、与标准库的区别

1)性能优化:STL 中的实现 会针对不同场景进行性能优化,例如 迭代器实现、内存分配策略、节点的存储方式等

2)异常安全性:STL 的容器 通常提供了强异常安全性,即在发生异常时,容器状态不会被破坏。这涉及到 对节点插入、删除等操作的异常处理

3)分配器支持:STL 容器 允许用户提供自定义的分配器,以适应不同的内存管理需求
特殊的内存需求:在某些嵌入式系统或实时系统中,默认的内存分配器可能不满足性能或内存布局的要求(预先分配 一大块内存,按需划分小块 给容器使用,减少频繁的内存分配和释放操作)
内存池管理:为了减少内存碎片或提高分配效率,可以使用内存池技术

std::allocator:这是 C++ 标准库提供的 默认分配器,用于 在自由存储区(通常是堆)上分配和释放内存
分配器接口:自定义分配器 需要实现特定的接口,包括 类型定义和成员函数,以便与 STL 容器协同工作

自定义分配器需要遵循标准分配器的接口要求,通常需要 实现以下类型和函数:
1、类型定义
value_type:要分配的对象类型
pointer、const_pointer、void_pointer、const_void_pointer:指针类型定义
difference_type、size_type:用于指针差值和大小的类型
rebind:模板结构,用于 为其他类型创建分配器(假设 有一个模板分配器 MyAllocator<T>,它用于类型 T。当 STL 容器需要为类型 U 分配内存时,可以通过 rebind 获取一个新的分配器类型 MyAllocator<U>
在 STL 容器的实现中,通常会这样使用:

// 假设 Alloc 是分配器类型,T 是容器元素类型,U 是需要分配内存的其他类型
typename Alloc::template rebind<U>::other alloc_u;
// 自定义分配器
template <typename T>
class MyAllocator {
public:
    using value_type = T;

    // 其他必要的类型定义和成员函数...

    // rebind 模板结构
    template <typename U>
    struct rebind {
        using other = MyAllocator<U>;
    };
};

// 使用 rebind
// 原始分配器类型
MyAllocator<int> alloc_int;

// 需要为 double 类型分配内存,使用 rebind 获取新的分配器类型
MyAllocator<double>::rebind<double>::other alloc_double;

2、成员函数
构造函数和析构函数:用于初始化分配器对象
allocate:分配内存的函数
deallocate:释放内存的函数
max_size:可分配的最大对象数量
construct(C++17 前)和 destroy(C++17 前):构造和析构对象的函数,在 C++17 中,construct 和 destroy 成员函数已被弃用,容器直接使用 ::new 和 调用析构函数来构造和销毁对象

自定义分配器示例

#include <memory>
#include <iostream>

// 自定义分配器
template <typename T>
class LoggingAllocator {
public:
    using value_type = T;

    LoggingAllocator() = default;

    template <typename U>
    constexpr LoggingAllocator(const LoggingAllocator<U>&) noexcept {}

    T* allocate(std::size_t n) {
        std::cout << "分配 " << n << " 个对象,大小为 " << n * sizeof(T) << " 字节。" << std::endl;
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }

    void deallocate(T* p, std::size_t n) noexcept {
        std::cout << "释放 " << n << " 个对象,大小为 " << n * sizeof(T) << " 字节。" << std::endl;
        ::operator delete(p);
    }
};

// 分配器的比较操作符
template <typename T, typename U>
bool operator==(const LoggingAllocator<T>&, const LoggingAllocator<U>&) { return true; }

template <typename T, typename U>
bool operator!=(const LoggingAllocator<T>&, const LoggingAllocator<U>&) { return false; }

使用自定义分配器的容器:

#include <vector>

int main() {
    // 使用自定义分配器的 vector
    std::vector<int, LoggingAllocator<int>> vec;

    vec.push_back(10);
    vec.push_back(20);
    vec.push_back(30);

    return 0;
}

输出示例:

分配 1 个对象,大小为 4 字节。
分配 2 个对象,大小为 8 字节。
释放 1 个对象,大小为 4 字节。
分配 4 个对象,大小为 16 字节。
释放 2 个对象,大小为 8 字节。
释放 4 个对象,大小为 16 字节。

4)迭代器的复杂性:STL 迭代器通常支持多种类型的迭代,包括正向迭代、逆向迭代(允许自增(++)操作,但方向与正向迭代器相反(即移动时向前遍历),访问当前元素(*for (std::vector<int>::reverse_iterator it = vec.rbegin(); it != vec.rend(); ++it))、常量迭代(不允许修改元素 for (std::vector<int>::const_iterator it = vec.cbegin(); it != vec.cend(); ++it))、逆向常量迭代器 for (std::vector<int>::const_reverse_iterator it = vec.crbegin(); it != vec.crend(); ++it)

输入迭代器:只能单向遍历,并且只能读取元素,常用于输入流,如 std::istream_iterator

#include <iostream>
#include <iterator>

int main() {
    std::cout << "请输入三个整数: ";
    std::istream_iterator<int> inIter(std::cin); // 从标准输入中读取
    std::istream_iterator<int> end; // 输入结束标记

    // 读取三个整数
    int a = *inIter++;
    int b = *inIter++;
    int c = *inIter++;

    std::cout << "你输入的整数是: " << a << ", " << b << ", " << c << std::endl;

    return 0;
}

输出:

请输入三个整数: 10 20 30
你输入的整数是: 10, 20, 30

输出迭代器:只能单向遍历,且只能写入元素,常用于输出流,如 std::ostream_iterator

#include <iostream>
#include <iterator>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    std::ostream_iterator<int> outIter(std::cout, " "); // 输出到标准输出
    for (int val : vec) {
        *outIter++ = val; // 写入数据到输出流
    }
    std::cout << std::endl;

    return 0;
}

输出:
1 2 3 4 5

正向迭代器:支持单向遍历,既可以读取也可以写入元素,如 std::forward_list

#include <iostream>
#include <forward_list>

int main() {
    std::forward_list<int> flist = {1, 2, 3, 4, 5};

    // 使用正向迭代器遍历并修改元素
    for (std::forward_list<int>::iterator it = flist.begin(); it != flist.end(); ++it) {
        std::cout << *it << " "; // 读取元素
        *it = *it * 2; // 修改元素
    }
    std::cout << std::endl;

    // 打印修改后的列表
    for (const auto& item : flist) {
        std::cout << item << " ";
    }
    std::cout << std::endl;

    return 0;
}

输出:

1 2 3 4 5
2 4 6 8 10

双向迭代器:支持正向和逆向遍历,既可以读取也可以写入元素,如 std::list

#include <iostream>
#include <list>

int main() {
    std::list<int> mylist = {10, 20, 30, 40, 50};

    // 正向遍历
    std::cout << "正向遍历: ";
    for (std::list<int>::iterator it = mylist.begin(); it != mylist.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 逆向遍历
    std::cout << "逆向遍历: ";
    for (std::list<int>::reverse_iterator rit = mylist.rbegin(); rit != mylist.rend(); ++rit) {
        std::cout << *rit << " ";
    }
    std::cout << std::endl;

    return 0;
}

输出:

正向遍历: 10 20 30 40 50
逆向遍历: 50 40 30 20 10

随机访问迭代器:支持随机访问,类似于指针,可以通过下标访问任意位置的元素,如 std::vector

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {10, 20, 30, 40, 50};

    // 随机访问元素
    std::cout << "第一个元素: " << vec[0] << std::endl;
    std::cout << "第三个元素: " << vec[2] << std::endl;

    // 通过迭代器进行随机访问
    std::vector<int>::iterator it = vec.begin();
    it += 3; // 跳转到第四个元素
    std::cout << "通过迭代器跳转到第四个元素: " << *it << std::endl;

    return 0;
}

输出:

第一个元素: 10
第三个元素: 30
通过迭代器跳转到第四个元素: 40

5)比较器支持:STL 允许用户 提供自定义的比较器,以支持不同的元素比较方式。这份简化代码 使用了默认的比较器
自定义比较器的形式(代码随想录第十三天 | 队列的应用:单调队列(leetcode 239),优先级队列(leetcode 347),栈和队列总结 见 2.1 leetcode 347
函数对象:通过重载 operator() 实现,通常为一个类或结构体

#include <iostream>
#include <set>
// 自定义比较器:降序排序
struct CustomCompare {
    bool operator()(int a, int b) const {
        return a > b; // 降序排序:当 a > b 时返回 true
    }
};

int main() {
	std::set<int, CustomCompare> myset = {1, 5, 2, 4, 3};

函数指针:可以直接传递比较的函数

// 自定义比较器函数:按降序排序
bool customCompare(int a, int b) {
    return a > b; // 降序排序
}

std::set<int, bool(*)(int, int)> myset(customCompare);

Lambda 表达式:简洁易用的方式,用于定义临时的比较逻辑

std::sort(vec.begin(), vec.end(), [](int a, int b) {
	return a > b; // 降序排序
});

6)分支预测和内联优化:STL 实现通常会利用分支预测、内联优化等技术,以提高代码执行效率

分支预测:现代处理器用来减少分支指令(如 if-else 或循环跳转)带来的性能损失的一项硬件技术。分支预测的基本思路是,处理器 会猜测下一条指令的执行路径,以便在预测成功时 能够继续执行 而不等待条件判断的结果。如果 预测失败,则需要回滚到正确的分支,这会导致性能损失

内联优化:编译器通过将函数调用的代码直接插入到调用点,避免了函数调用的额外开销(如堆栈管理、参数传递等)。在 C++ 中,内联优化通常与 inline 关键字相关,但实际上编译器 会自动决定哪些函数适合内联化

7)迭代器失效处理:STL 中的容器 在元素插入和删除时 会处理迭代器的失效问题(如:std::vector:插入操作 会使插入点及其后的迭代器失效;扩容时,所有迭代器 失效),以确保迭代器的正确性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值