简易STL实现 | Multiset 的实现

news2024/10/9 2:29:22

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:插入操作 会使插入点及其后的迭代器失效;扩容时,所有迭代器 失效),以确保迭代器的正确性

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2197774.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C语言】指针练习题

一、指针指向问题 int main() {int a[5] { 1, 2, 3, 4, 5 };int* ptr (int*)(&a 1);printf("%d,%d", *(a 1), *(ptr - 1));return 0; } 结果为&#xff1a;2&#xff0c;5。&a是整个数组&#xff08;&a 1&#xff09;被强转为&#xff08;int*&am…

mujoco版本问题以及ERROR: Failed building wheel for mujoco-py

问题&#xff1a; ERROR: Failed building wheel for mujoco-py Failed to build mujoco-py ERROR: Could not build wheels for mujoco-py, which is required to install pyproject.toml-based projects 起因&#xff1a; 一开始我使用这个命令安装pip install mujoco_py&…

跨境卖家品牌出海要注意哪些方面

随着目前互联网的发展&#xff0c;市场由线下扩张到全国&#xff0c;再扩张到了全球&#xff0c;但是海外市场和国内并不相同跨境卖家品牌想要出海&#xff0c;需要注意多个方面&#xff0c;以确保能够在国际市场上成功立足并发展。以下是一些关键点&#xff1a; 首先想得拥有…

2-116 基于matlab的主成分分析(PCA)及累积总和(CUSUM)算法故障监测

基于matlab的主成分分析&#xff08;PCA&#xff09;及累积总和&#xff08;CUSUM&#xff09;算法故障监测&#xff0c;针对传统的多元统计分析方法对生产过程中微小故障检测不灵敏的问题&#xff0c;使用基于主元分析的累积和的微小故障检测方法进行故障监测&#xff0c;通过…

微信卸载后聊天记录全部消失,重新安装后有方法恢复吗?

微信作为我们日常沟通的重要工具&#xff0c;其聊天记录往往承载着许多珍贵的回忆和重要的信息。然而&#xff0c;在日常使用手机的过程中&#xff0c;我们时常会出于清理内存、解决软件故障或尝试新版本等原因&#xff0c;选择卸载并重新安装微信app。然而&#xff0c;这一简单…

国家公务员考试倒计时页面介绍

代码复刻 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>国家公务员考试倒计时</title><…

malloc(0)

malloc(0) 在操作系统底层的实现涉及内存分配管理的多个方面。下面是对 malloc(0) 的实现原理的详细解释&#xff1a; 1. 内存分配管理 操作系统通过内存管理子系统来处理内存分配请求&#xff0c;包括 malloc 函数。内存分配通常使用以下几种策略&#xff1a; 堆管理&#…

OpenFegin

文章目录 一、OpenFegin是什么&#xff1f;二、基本使用三、超时重试机制4.自定义超时重传机制五、底层实现 一、OpenFegin是什么&#xff1f; OpenFeign的全称为Spring Cloud OpenFeign(下文简称OpenFeign),是Spring Cloud团队开发的一款基于 Feign的框架&#xff0c;声明式W…

Overleaf 无法显示图片

问题描述 在Overleaf中的代码为&#xff1a; \begin{figure}\centering\includegraphics[width0.98\linewidth]{figures/test.png}\caption{This is a test.}\label{fig:test} \end{figure}但无法正常显示图片&#xff1a; 解决方案 修改编译模式为正常Normal而非快速Fast …

Python 工具库每日推荐【python-docx】

文章目录 引言Python 文档处理库的重要性今日推荐:Python-docx 工具库主要功能:使用场景:安装与配置快速上手示例代码代码解释实际应用案例案例:自动生成个性化证书案例分析高级特性样式应用表格操作扩展阅读与资源优缺点分析优点:缺点:总结【 已更新完 TypeScript 设计模…

八大排序--03插入排序

假设数组 arr[] {5,7,4,2,0,1,6},请通过插入排序的方式&#xff0c;实现从小到大排列&#xff1a; 方法&#xff1a;插入排序默认待排数组中的第一个是已经排好序的数值&#xff1b;定义游标从第二个数据开始不断向后方进行遍历&#xff0c;并将游标指向的数据不断插入到排好序…

探索Ultralytics YOLO11在视觉任务上的应用

前言 在人工智能持续发展的当下&#xff0c;有一点是确凿无疑的&#xff1a;模型正变得愈发优秀、快捷和智能。就在人们以为YOLO系列已登峰造极之时&#xff0c;Ultralytics推出了最新升级版——YOLO11。需要注意的是&#xff0c;这里不是YOLOv11&#xff0c;他们简化了命名方…

秋季猫咪掉毛严重怎么办?宠物空气净化器到底有没有用?

告别炎热的夏天&#xff0c;秋意随着家里猫咪新一轮的掉毛一起到来。我家两只布偶齐齐发力&#xff0c;疯狂掉毛&#xff0c;家里每个角落无一幸免。衣服上、地板上&#xff0c;肉眼可见家里的毛发量在不断增多&#xff0c;又陷入了日复一日的清理大战。除此之外&#xff0c;对…

erlang学习:Linux命令学习10

从百度网盘下载文件 共享百度网盘获得链接 https://pan.baidu.com/s/1iUOTAWr1SRlL2fBZ7lIV拿到链接之后在浏览器中进行下载&#xff0c;可以查看下载链接 右键这些文件即可得到下载链接 类似于长这样 https://bdbl-cm01.baidupcs.com/file/b02f72906b3d0d07130be625eabc76…

12306积分换的一等座还有零食

“12306积分换的一等座还有零食&#xff01;”这可真是个意外的惊喜呢&#xff01;平时积累的小积分&#xff0c;竟然能兑换到如此舒适的一等座车票&#xff0c;而且还附赠了精致的小零食&#xff0c;真是让人倍感贴心与满足。这样的体验&#xff0c;不仅让旅途变得更加惬意&am…

YOLOv8实战TT100K中国交通标志检测【数据集+YOLOv8模型+源码+PyQt5界面】

YOLOv8实战TT100k交通标志识别 文章目录 研究背景资源获取1.前言1.1 YOLO 系列&#xff1a;中国交通标志检测领域的璀璨明星1.2 Transformer与注意力机制&#xff1a;为中国交通标志检测注入新活力1.3 中国交通标志检测技术&#xff1a;迎接挑战&#xff0c;砥砺前行1.4 YOLOv8…

刷题训练之解决 FloodFill 算法

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;熟练掌握解决 FloodFill 算法。 > 毒鸡汤&#xff1a;学习&#xff0c;学习&#xff0c;再学习 ! 学&#xff0c;然后知不足。 > 专栏选自&#…

面试(十)

目录 一. 单元测试 二. FreeRTOS和裸机哪个实时性好&#xff1f; 三. 怎么判断某个程序的运行时间 四. 函数指针 五. 全局变量被线程使用冲突 5.1 使用互斥锁 5.2 使用读写锁 5.3 使用原子操作 六. 局部变量没有初始化是什么值 七. uint_8 n 255 , n等于多少 八. …

利基营销:如何为小众受众制定内容营销策略?AIGC大模型创新思维数字化转型商业模式专家培训讲师谈短视频内容社私域数字经济人工智能

了解利基营销 什么是利基营销&#xff1f; 简单来说&#xff0c;利基营销就是专注于特定范围的潜在客户群&#xff0c;而不是针对广泛的人群。 实际上&#xff0c;利基营销可以比作为拥有露营装备的人而不是所有热爱户外活动的人定制内容。露营爱好者会欣赏专门针对他们的需…

最佳实践(1)

1.Java 集合概览 Java 集合&#xff0c;也叫作容器&#xff0c;主要是由两大接口派生而来&#xff1a;一个是 Collection接口&#xff0c;主要用于存放单一元素&#xff1b;另一个是 Map 接口&#xff0c;主要用于存放键值对。对于Collection 接口&#xff0c;下面又有三个主要…