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

本文详细介绍了C++STL中的关联容器Map和Set,重点讨论了它们的内部数据组织方式——红黑树,以及如何封装Map和Set。Map通过键值对提供一对一的数据处理,Set则是键的集合,两者都基于红黑树实现,保证数据的有序性。文章还探讨了红黑树的节点结构、迭代器的实现以及插入操作,并提供了简单的测试用例来验证理解和实现。
摘要由CSDN通过智能技术生成

"湖人总冠军"

一、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;
        }
    }

总结:

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

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

祝你好运,向阳而生~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值