新C++(10):Map\Set的封装

news2024/11/18 15:47:04

"湖人总冠军"

一、Map\Set的介绍

Set是C++标准库中的一种关联容器。所谓关联容器就是通过键(key)来读取和修改元素。与map关联容器不同,它只是单纯键的集合。
取自这里
Map是STL 的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据处理能力,由于这个特性,它完成有可能在我们处理一对一数据的时候,在编程上提供快速通道。
取自这里
说下map内部数据的组织, map内部自建一颗红黑树(一种非严格意义上的平衡二叉树), 这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的,后边我们会见识到有序的好处。
取自这里

在map、set没出来之前呢,我们存储数据常用的都是顺序表、链表、栈队列等这些都被称为"序列式容器"。如果我们想要存储一些关联式键值对,例如"名字:身份证号","商品名称:商品数量"等等以这样数值建立起来的<Key,Value>模型,在数据检索时比序列式容器效率更高

二、封装Map、Set

(1)红黑树

我们先来看看STL源码中,是如何处理结点的值。

    template<class Value>
    struct RBTreeNode
    {
        RBTreeNode<Value*> _left;
        RBTreeNode<Value*> _right;
        RBTreeNode<Value*> _parent;
        Value _data;

        Color _color;
        RBTreeNode(const Value& data)
            :_data(data),
            _left(nullptr),
            _right(nullptr),
            _parent(nullptr),
            _color(RED)
        {}
    };

我们之前实现的Node结点都是<K,V>模型,为什么源码中会这样声明了呢?在之后封装的时候会解答。

(2)红黑树类

同样,我们先拿源码来看看~

Key:键值
Value:值
KeyOfValue:取键值里的值

这里有几个疑问:

为什么 还会需要传Key这个参数?

为什么难道我们在Value里找不到Key值嘛?

为什么需KeyOfValue?

假设我们使用map,传入的一定是一个"pair值",那么map的数值传入到底层红黑树这一层,是传递给Key,还是Value呢?当然是Value!(之后会解答) 而我们比较的是键值(key)还是值(value)?是键值(key),所以我们传入键值与这个传入Value有必然的关联吗?肯定是没有的。

上述问题也就能够简略地回答这么设计的两个疑问。
那为什么需要KeyOfValue呢?因为C++中pair内置的"比较重载"很恶心,它不仅仅只会比较kv.first,还会比较kv.second。但是,我们只需要注意pair中的first。
    template<class K,class Value,class KeyOfValue>
    class RBTree
    {
        typedef RBTree<Value> Node;
        KeyOfValue kot;
        //.....
         
    }

(3)红黑树迭代器

    template<class Value,class Ref,class Ptr>
    struct __RBTreeIterator__
    {
        typedef __RBTreeIterator__<Value, Ptr, Ref> Self;
        typedef __RBTreeIterator__<Value, const Value&, const Value*> const_iterator;
        typedef __RBTreeIterator__<Value, Value&, Value*> iterator;
        typedef RBTreeNode<Value> Node;
        Node* _node;

        __RBTreeIterator__(const Node& node)
            :_node(node)
        {}

        Ref operator*()
        {
            return _node->_data;
        }

        Ptr operator->()
        {
            return &_node->_data;
        }

        bool operator!=(const Self& s)
        {
            return _node != s._node;
        }

        bool operator==(const Self& s)
        {
            return _node == s._node;
        }
    };

那么如何进行++、--呢?


        Self& operator++()
        {
            if (_node->_right)
            {
                //1.找到 右子树的最左节点
                Node* left = _node->_right;
                while (left->_left)
                {
                    left = left->_left;
                }
                _node = left;
            }
            else
            {
                //2.向上调整
                Node* cur = _node;
                Node* parent = cur->_parent;
                while (parent && cur == parent->_right)
                {
                    cur = cur->_parent;
                    parent = parent->_parent;
                }
                _node = parent;
            }
            return *this;
        }

        Self& operator--()
        {
            if (_node->_left)
            {
                //找左子树的最右节点
                Node* right = _node->_left;
                while (right->_right)
                {
                    right = right->_right;
                }
                _node = right;
            }
            else
            {
                Node* cur = _node;
                Node* parent = cur->_parent;
                while (parent && cur == parent->_left)
                {
                    cur = cur->_parent;
                    parent = parent->_parent;
                }
                _node = parent;
            }
        }

const迭代器与非const迭代器的转换:

在STl中,存在这样的转换。但是我们模拟实现的这一部分却不支持这样。那么如何理解源码那一份拷贝构造的代码呢?

        __RBTreeIterator__(const iterator& it)
            :_node(it._node)
        {}

(4)Map\Set封装

不过在这之前,我们已经实现了一份红黑树的迭代器,我们正好可以继续完善原RBTree的代码,

    template<class K,class Value,class KeyOfValue>
    class RBTree
    {
    public:
        typedef RBTreeNode<Value> Node;
        //普通迭代器 const迭代器
        typedef __RBTreeIterator__<Value, Value&, Value*> iterator; 
        typedef __RBTreeIterator__<Value, const Value&, const Value*> const_iterator;
    protected:
        KeyOfValue kot;      //比较函数
    public:
        iterator begin()
        {
            //找左节点
            Node* left = _root;
            while (left->_left)
            {
                left = left->_left;
            }
            return iterator(left);
        }

        iterator end()
        {
            return iterator(nullptr);
        }

        const_iterator begin()const
        {
            Node* left = _root;
            while (left->_left)
            {
                left = left->_left;
            }
            return const_iterator(left);
        }

        const_iterator end()const
        {
            return const_iterator(nullptr);
        }

Set:

    template<class K>
    class Set
    {
    public:
        struct  SetKeyOfValue
        {
            bool operator()(const K& key)
            {
                //如果是set的Key 直接返回就行了
                return key;
            }
        };
        //typename是向编译器声明 把类部类里的模板 当成一个对象
        typedef typename RBTree<K, K, SetKeyOfValue>::const_iterator iterator;
        typedef typename RBTree<K, K, SetKeyOfValue>::const_iterator const_iterator;

        iterator begin()const
        {
            return _set.begin();
        }

        iterator end()const
        {
            return _set.end();
        }

        std::pair<iterator, bool> insert(const K& key)
        {
            //std::pair<typename RBTree<K, K, SetKeyOfValue>::iterator, bool> ret = _set.Insert(key);
            auto ret = _set.Insert(key);
            return std::make_pair(ret.first, ret.second);
        }

    private:
        RBTree<const K, K, SetKeyOfValue> _set;
    };

map;

    template<class K,class V>
    class Map
    {
    public:
        struct MapKeyOfValue
        {
            const K& operator()(const std::pair<K,V>& kv)
            {
                return kv.first;
            }
        };

        typedef typename RBTree<K, std::pair<const K, V>, MapKeyOfValue>::iterator iterator;
        typedef typename RBTree<K, std::pair<const K, V>, MapKeyOfValue>::const_iterator const_iterator;

        std::pair<iterator, bool> insert(const std::pair<K, V>& kv)
        {
            return    _map.Insert(kv);
        }

        V& operator[](const K& key)
        {
            //auto ret = _map.Insert(make_pair(key, V()));
            std::pair<typename RBTree<K,std::pair<K,V>,MapKeyOfValue>::iterator,bool> ret = _map.Insert(make_pair(key, V()));
            return ret.first->second;
        }

    private:
        RBTree<const K, std::pair<const K, V>, MapKeyOfValue> _map;
    };

(5)map、set调用过程

虽然画起来错综、杂糅,但是如果真的理解到了其实也还好。

三、测试

我们分别对set、map进行测试。

    void TestSet()
    {
        std::cout << "TestSet" << std::endl;
        int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
        Set<int> s;
        for (auto e : a)
        {
            s.insert(e);
        }

        Set<int>::iterator it = s.begin();
        while (it != s.end())
        {
            std::cout << *it << " ";
            ++it;
        }
        std::cout << std::endl;
    }

    void TestMap()
    {
        std::cout << "Map For Test" << std::endl;
        std::string arr[] = { "苹果","苹果", "苹果", "梨儿","梨儿","西瓜","西瓜" };
        Map<std::string, int> CountMap;
        for (auto e : arr)
        {
            CountMap[e]++;
        }

        for (auto& kv : CountMap)
        {
            std::cout << kv.first << ":" << kv.second << std::endl;
        }
    }

总结:

我们并非是要造轮子,阅读源码,汲取前辈们的智慧设计。

本篇到此为止,感谢你的阅读。

祝你好运,向阳而生~

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

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

相关文章

第二回:艺术画笔见乾坤

import numpy as np import pandas as pd import re import matplotlib import matplotlib.pyplot as plt from matplotlib.lines import Line2D from matplotlib.patches import Circle, Wedge from matplotlib.collections import PatchCollection一、概述 1. matplotlib…

软件测试:用“bug”来表示“在电脑程序里的错误”

计算机基础知识计算机&#xff08;personal computer&#xff09;俗称电脑&#xff08;pc&#xff09;&#xff0c;是现代一种用于高速计算的电子机器&#xff0c;可以进行数值计算&#xff0c;又可以进行逻辑判断&#xff0c;还具有存储记忆功能&#xff0c;且能够按照程序的运…

【模拟集成电路】频率综合器(Frequency Synthesizer,FS)设计

应用于无线局域网的频率综合器设计前言频率综合器简介各部分链接链接&#xff1a;前言 本文主要内容是对频率综合器或称为PLL 做出简单介绍&#xff0c;为课程设计部分章节内容&#xff0c;后需给出各部分的设计方案&#xff0c;以及测试结果。 频率综合器简介 无线收发系统中…

跳槽进字节跳动了,面试真的很简单

前言: 最近金三银四跳槽季&#xff0c;相信很多小伙伴都在面试找工作&#xff0c; 怎样才能拿到大厂的offer&#xff0c;没有掌握绝对的技术&#xff0c;那么就要不断的学习 如何拿下阿里等大厂的offer的呢&#xff0c;今天分享一个秘密武器&#xff0c;资深测试工程师整理的…

Elasticsearch7.8.0版本进阶——持久化变更

目录一、持久化变更的概述二、事务日志&#xff08;translog&#xff09;三、持久化变更完整流程四、事务日志&#xff08;translog&#xff09;的作用五、事务日志&#xff08;translog&#xff09;的目的一、持久化变更的概述 没有用 fsync 把数据从文件系统缓存刷&#xff…

随机森林算法(Random Forest)R语言实现

随机森林1. 使用Boston数据集进行随机森林模型构建2. 数据集划分3.构建自变量与因变量之间的公式4. 模型训练5. 寻找合适的ntree6. 查看变量重要性并绘图展示7. 偏依赖图:Partial Dependence Plot&#xff08;PDP图&#xff09;8. 训练集预测结果1. 使用Boston数据集进行随机森…

【华为OD机试模拟题】用 C++ 实现 - 分糖果(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

【华为OD机试模拟题】用 C++ 实现 - 时间格式化(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

匈牙利算法与KM算法的区别

前记 在学习过程中&#xff0c;发现很多博客将匈牙利算法和KM算法混为一谈&#xff0c;当时只管用不管分析区别&#xff0c;所以现在来分析一下两个算法之间的区别。 匈牙利算法在二分图匹配的求解过程中共两个原则&#xff1a; 1.最大匹配数原则 2.先到先得原则 而KM算法求…

Linux centos升级nodejs,解决升级NodeJS遇到的问题,升级GLIBC、GLIBCXX、gcc(含资源包下载)

公司网站用的Nuxt开发的&#xff0c;本地开发环境NodeJS已经升级到16.14.2版本&#xff0c;服务器也要从12版本升级到16.14.2 如需本次安装的资源&#xff0c;请下滑到文章下面下载整套资源 NodeJS版本下载地址&#xff1a;https://nodejs.org/dist/v16.14.2 解压安装node后…

Docker 应用实践-仓库篇

目前 Docker 官方维护了一个公共仓库 Docker Hub&#xff0c;用于查找和与团队共享容器镜像&#xff0c;界上最大的容器镜像存储库&#xff0c;拥有一系列内容源&#xff0c;包括容器社区开发人员、开放源代码项目和独立软件供应商&#xff08;ISV&#xff09;在容器中构建和分…

【涨薪技术】0到1学会性能测试 —— 分类及应用领域

上一次推文我们分享了性能测试相关的专业术语&#xff0c;今天我们来看下性能测试的分类及应用领域!后续文章都会系统分享干货&#xff0c;带大家从0到1学会性能测试&#xff0c;另外还有教程等同步资料&#xff0c;文末获取~ 性能测试划分 01、负载测试 负载测试是指服务器最…

【华为OD机试模拟题】用 C++ 实现 - 商人买卖(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

黑客网站大全!都在这了!速看被删就没了

我们学习网络安全&#xff0c;很多学习路线都有提到多逛论坛&#xff0c;阅读他人的技术分析帖&#xff0c;学习其挖洞思路和技巧。但是往往对于初学者来说&#xff0c;不知道去哪里寻找技术分析帖&#xff0c;也不知道网络安全有哪些相关论坛或网站&#xff0c;所以在这里给大…

java延时队列

二、延时队列使用场景 那么什么时候需要用延时队列呢&#xff1f;常见的延时任务场景 举栗子&#xff1a; 订单在30分钟之内未支付则自动取消。重试机制实现,把调用失败的接口放入一个固定延时的队列,到期后再重试。新创建的店铺&#xff0c;如果在十天内都没有上传过商品&…

XML调用 CAPL Test Function

&#x1f345; 我是蚂蚁小兵&#xff0c;专注于车载诊断领域&#xff0c;尤其擅长于对CANoe工具的使用&#x1f345; 寻找组织 &#xff0c;答疑解惑&#xff0c;摸鱼聊天&#xff0c;博客源码&#xff0c;点击加入&#x1f449;【相亲相爱一家人】&#x1f345; 玩转CANoe&…

数据结构:完全二叉树开胃菜小练习

目录 一.前言 二.完全二叉树的重要结构特点 三.完全二叉树开胃菜小练习 1.一个重要的数学结论 2.简单的小练习 一.前言 关于树及完全二叉树的基础概念(及树结点编号规则)参见:http://t.csdn.cn/imdrahttp://t.csdn.cn/imdra 完全二叉树是一种非常重要的数据结构: n个结点的…

22-基于分时电价条件下家庭能量管理策略研究MATLAB程序

参考文献&#xff1a;《基于分时电价和蓄电池实时控制策略的家庭能量系统优化》参考部分模型《计及舒适度的家庭能量管理系统优化控制策略》参考部分模型主要内容&#xff1a;主要做的是家庭能量管理模型&#xff0c;首先构建了电动汽车、空调、热水器以及烘干机等若干家庭用户…

【C++入门第二期】引用 和 内联函数 的使用方法及注意事项

前言引用的概念初识引用区分引用和取地址引用与对象的关系引用的特性引用的使用场景传值和引用性能比较引用和指针的区别内联函数内联函数的概念内联函数的特性前言 本文主要学习的是引用 及 内联含函数&#xff0c;其中的引用在实际使用中会异常舒适。 引用的概念 概念&…

基于SpringBoot的企业资产管理系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏…