map和set的底层就是红黑树,所以只要实现了红黑树,就可以考虑封装出map和set。所以本文会重点介绍我是如何一步步封装map和set,就不会再介绍红黑树的实现,实现可看我上篇博客,个人觉得封装中最麻烦的就是模板参数和迭代器,所以本文从这两个方面入手。红黑树博客链接在下:
map和set都是模板,使用时都要先显示实例化 , set只要传一个类型实例化K即可,因为set中只存一个关键码-key , 而map要给K , T实例化成具体的类型(K,T分别对应map K-V结构的关键码-key和存的数据-value) , 那为什么map的模板参数不命名为K,V呢?这其实牵扯到一个问题,那就是树中存的是什么?(红黑树模板参数会提及)


map和set的底层就是红黑树,那是如何将红黑树的函数给map和set呢, 可以用继承,或者是组合。至于为什么此时用组合呢?我觉得在map和set内部定义了一个红黑树变量(这种方式称为组合),比继承的耦合度更低,或许可以减少后期维护的工作量。而这个红黑树也是一个模板,所以定义该变量也要传模板参数。如下
红黑树接收的模板参数如下:
T是节点存的数据类型,K就是关键码key的数据类型,第三个模板参数先不用理会。

当我们大致解释了红黑树的模板参数的意义,也就知道了第一个模板参数是set和map的关键码key的数据类型, 第二个是map和set在节点存的数据类型。
set给红黑树传的模板参数如下:

set(set是把K传给红黑树的模板参数T实例化),set库里把K重命名为value然后传给T(T指的是红黑树模板参数),而map是把一个pair重命名为value传给T,这种重命名为value个人觉得是为了统一记忆 , value是外部传给红黑树节点的数据类型 , 免得一会是K,一会是pair。
为了避免混淆,不让大家以为map模板参数V是value_type的缩写,所以库里的map的模板参数是K和T,而不是K和V,我们为了与库里命名,函数实现保持一致,便于我们理解库里的实现,也用K ,T做map的模板参数。
(set官网文档如下图)

(map官网文档如下图)

map给红黑树传的模板参数如下:

那为什么map传给红黑树的T不是自己模板参数的T呢,而是一个pair呢,如果传的是自己的模板参数T,这样K,T不都传过去了吗?传pair不是浪费吗?
可是你看看下面节点类RBTreeNode


而map是k-v结构的,例如统计字符串出现次数, 那map实例化应该是map
我们先前勉强了解了红黑树模板参数T,map传一个pair类型给T接收 , 而set传的是一个k类型。

我们可以看到的是RBTree的模板参数还有两个。由于insert有可能是插入一个pair,也有可能是插入一个key值,由此却引发另外一个问题 , 那就是insert插入元素是要做大量的判断的,map插入的data是pair类型的,按二叉搜索树规则,要当前根节点cur比较key的值找插入位置,而cur是要用cur->_data.first才能取到key的值,而data则也要用.first访问到key,但是set插入的数据就是key的值,可以直接用cur->_data和插入的data比较,此时难道又要写两份代码吗?当然不是,所以我们就用一个仿函数将pair中的key返回来,而set为了保持一致,也得传个仿函数,只是这个仿函数几乎不做处理,这就是第三个模板参数的作用。
Set仿函数如下:
没有做什么处理就直接返回

Map仿函数如下:
取出key返回,此处是set为了配合map做出的让步,后续我们会在迭代器处看到map和set的其它冲突。在多种冲突下能维持一种特殊的平衡是一件很有成就感的事。

使用场景如下:(在比较处就用这个仿函数)

好好好,那为什么还要传个k呢,不是多余吗 , 那你想想find的参数那个const K& key中的K是哪来的,如果不传K,那find的参数类型如何确定,那个仿函数可取不出来,取得可不是类型,那是数据。
还有个难啃的骨头,set和map的key可修改的问题,但是只要我们把key可修改和模板参数的问题理清了,封装就过去了。
const迭代器是节点数据不能修改, 普通迭代器则可以修改节点数据,但是set中的节点数据只有key关键码,key是不能修改的, 所以set的普通迭代器也不能修改这个关键码,也就是说我们要让set的const迭代器和普通迭代器保持一致。
迭代器iterator本质是my_set类和my_map类内部的一个类型,然后用这个类型来定义变量使用。
My_Structure::my_set<int>::iterator sit = s1.begin();
所以要想取出来的iterator这个类型定义出来的变量,有const_iterator的作用,我们就得对const_iterator进行typedef重命名,将const_iterator命名为iterator,这样就有种偷梁换柱的效果,你以为取出来的iterator是普通迭代器,其实是const_iterator假扮的(如下)。

所以set只提供加了const的begin和end函数,这样不管外部的set变量是不是const的,都只会调用这个被const修饰的begin和end函数,也就只会返回const迭代器了。注:此处的iterator是const_iterator重命名的。

map就不能用set的方法了,因为这样map的pair就是有const属性的了,那first是key不被修改可以,second是有被修改的需求的,一刀切是不行的。大佬的想法总是让人意想不到,那就是直接就把pair中的K传为const K, 这样普通迭代器就只会限制key,而不会限制value。

还有值得一提的是,这种方法为什么不用在set上呢,可能会限制某些功能的开发,所以大佬就没用,但是就我们这点功能还是可以对set用传const key的方法。

我们先记住这两个容器实现const迭代器的方法,由此产生的问题才刚刚开始,下面介绍map和set的成员函数会再说。
_t这个红黑树变量调用自己的insert函数返回的pair


大佬们想了一种办法就是,我们先用匹配类型接收,然后调用构造函数来转换类型(这一步可以在后面完整代码介绍再好好体会),这一步太妙了,我才意识到,构造函数的本质其实是一种类型的转换。
还要解释一点就是typename这个关键字的意思,因为我们要取的红黑树类内的iterator,所以要指定类域,但是这个类没有被实例化,编译器不知道你取出来的是静态变量还是一种类型,因为我们取静态变量也是用类域::这种方式访问,犹豫不决编译器就选择报错了,所以有了typename,告诉编译器你先别报错,我这肯定是类型,等我实例化你再核实。

类型匹配,不用做处理。

[]重载的妙用:map

set的insert,begin,end函数都介绍完了,下面完整的发出封装代码,代码不多。
- namespace My_Structure
- {
- template<class K>
- class my_set
- {
-
- struct GainSetKey 为了map和set传模板参数保持一致,set也要传一个GainSetKey
- {
- const K& operator()(const K&data)
- {
- return data;
- }
- };
- public:
-
- typedef typename RBTree
::const_iterator iterator; - typedef typename RBTree
::const_iterator const_iterator; -
- public:
- pair
bool > Insert(const K& data)//iterator:const_iterator - _t.Insert返回的是普通迭代器
- {
- pair<typename RBTree
::iterator,bool> - p1(_t.Insert(data));
-
- return pair
bool>(iterator(p1.first), p1.second); - }
-
- iterator begin()const//只提供const的begin和end函数
- {
- return _t.begin();
- }
- iterator end()const
- {
- return _t.end();
- }
- private:
- RBTree
_t; -
- 第一个模板参数k是给find使用的,第二个是给RBTree节点使用的
- };
- };
如下封装代码,map只剩下begin和end函数没提,其余的都讲过了,map的begin和end函数要提供const和非const的,如果只有const的,那返回就都是带const迭代器的pair了。
- namespace My_Structure
- {
- template<class K,class T>
- class my_map
- {
-
- struct GainMapKey//用来返回pair中的key
- {
- const K& operator()(const pair
& data) - {
- return data.first;
- }
- };
- typedef typename RBTree
const K, T>, GainMapKey>::iterator iterator; - typedef typename RBTree
const K, T>, GainMapKey>::const_iterator const_iterator; - public:
- pair
bool > Insert(const pair&data) //不能返回const迭代器,这样[]无法修改val - {
- return _t.Insert(data);
- }
- iterator begin()
- {
- return _t.begin();
- }
- iterator end()
- {
- return _t.end();
- }
- const_iterator begin()const
- {
- return _t.begin();
- }
- const_iterator end()const
- {
- return _t.end();
- }
- T& operator[](const pair
&data) - {
- iterator it=_t.Insert(data).first;
- return (it)->second;
- }
- private:
- RBTree
const K, T>, GainMapKey> _t; - };
- };
一个模板参数T,使得_data可以是任意类型的数据。
- enum Color
- {
- Red,
- Black
- };
- template<class T>
- struct RBTreeNode
- {
- RBTreeNode(const T& data) 默认构造
- :_data(data)
- ,_left(nullptr)
- ,_right(nullptr)
- ,_parent(nullptr)
- {
- ;
- }
- T _data; 节点数据类型根据模板参数而定,可能是一个pair,或者一个key
- RBTreeNode
*_left; - RBTreeNode
* _right; - RBTreeNode
* _parent; - Color _color=Red; 默认节点颜色为红
- };
-
-
- template<class K, class T,class Gainkey>
- class RBTree
- {
- public:
- typedef RBTreeNode
Node; - typedef _Iterator
iterator; - typedef _Iterator
const T*,const T&> const_iterator; const迭代器, -
- 第一个模板参数不传const T,这个T有大用。
- 而且const迭代器的不可修改特性是由后面const T*和const T&决定的,与第一个模板参数无关
-
- public:
- iterator begin() 树的最左节点是中序遍历的起点
- {
- Node* cur = _root;
- while (cur->_left)
- {
- cur = cur->_left;
- }
- return iterator(cur);
- }
- iterator end() 迭代器++最后到根的parent,也就是空节点
- {
- return iterator(_root->_parent);
- }
- const_iterator begin()const
- {
- Node* cur = _root;
- while (cur->_left)
- {
- cur = cur->_left;
- }
- const_iterator cit(cur);
- return cit;
- }
- const_iterator end()const
- {
- return const_iterator(nullptr);
- }
-
- public:
- pair
bool > Insert(const T& data) - {
- Gainkey get;
- if (_root == nullptr)
- {
- _root = new Node(data);
- _root->_color = Black;
- return pair
bool>(iterator(_root), true); - }
- Node* cur = _root;
- Node* parent = _root;
- while (cur) 按照搜索二叉树规则插入节点
- {
- if (get(cur->_data) < get(data))
-
- 若是map实例化,get可以将pair转为key来比较
-
- {
- parent = cur;
- cur = cur->_right;
- }
- else if (get(cur->_data) > get(data))
- {
- parent = cur;
- cur = cur->_left;
- }
- else
- {
- return pair
bool>(iterator(cur), false); -
- 找到相同节点,返回该节点迭代器
-
- }
- }
- //当cur等于nullptr时,
- //插入节点
- cur = new Node(data);
- Node* newnode = cur;
-
- if (get(parent->_data) > get(data))//小于parent的key,插入到左边
- {
- parent->_left = cur;
- }
- else //大于parent的key,插入到右边
- {
- parent->_right = cur;
- }
- cur->_parent = parent;
-
- //变色
- while (parent && parent->_color == Red)//当cur的父节点为空和颜色为黑时,调整结束
- {
- Node* gparent = parent->_parent;
- if (parent==gparent->_left)
- {
- Node* uncle = gparent->_right;
- if (uncle && uncle->_color==Red)//当uncle节点存在并且颜色为红时
- {
- uncle->_color = Black;
- parent->_color =Black;
- gparent->_color = Red;
- //继续向上调整
- cur = gparent;
- parent = cur->_parent;
- }
- else
- {
- if (cur == parent->_left)//右单旋
- {
- RotateR(gparent);
- parent->_color = Black;
- gparent->_color = Red;
- }
- if (cur == parent->_right)//双旋
- {
- RotateL(parent);
- RotateR(gparent);
- cur->_color = Black;
- gparent->_color = Red;
- }
- break;
- }
- }
- else//parent==gparent->_right
- {
- Node* uncle = gparent->_left;
- if (uncle && uncle->_color == Red)//当uncle节点存在并且颜色为红时
- {
- uncle->_color = Black;
- parent->_color = Black;
- gparent->_color = Red;
- //继续向上调整
- cur = gparent;
- parent = cur->_parent;
- }
- else
- {
- if (cur == parent->_right)//左单旋
- {
- RotateL(gparent);
- parent->_color = Black;
- gparent->_color = Red;
- }
- if (cur == parent->_left)
- {
- RotateR(parent);
- RotateL(gparent);
- cur->_color = Black;
- gparent->_color = Red;
- }
- break;
-
- }
- }
-
- }
- _root->_color = Black;
- return pair
bool>(iterator(newnode),true); - }
- void RotateR(Node* parent)
- {
- Node* cur = parent->_left;
- Node* Curright = cur->_right;
- //旋转
- Node* pparent = parent->_parent;//记录该树parent节点的父节点
- cur->_right = parent;
- parent->_left = Curright;
- parent->_parent = cur;
- cur->_parent = pparent;
-
- if (Curright)//cur的右子树不为空时
- {
- Curright->_parent = parent;
- }
-
-
- if (parent == _root)//改变根
- {
- _root = cur;
- }
- else//和上一层链接
- {
- if (pparent->_left == parent)
- pparent->_left = cur;
- else
- pparent->_right = cur;
- }
- }
- void RotateL(Node* parent)//左旋转
- {
- Node* cur = parent->_right;
- Node* Curleft = cur->_left;
- //旋转
- Node* pparent = parent->_parent;
- cur->_left = parent;
- parent->_right = Curleft;
- parent->_parent = cur;
- cur->_parent = pparent;
-
- if (Curleft)//cur的左子树不为空时
- {
- Curleft->_parent = parent;
- }
- if (parent == _root)//改变根
- {
- _root = cur;
- }
- else//和上一层链接
- {
- if (pparent->_left == parent)
- pparent->_left = cur;
- else
- pparent->_right = cur;
- }
- }
-
- Node* _root=nullptr;
- };

我们先前只说了set和map容器如何使得key不可修改,还没看const迭代器的实现,
下面有个构造函数先前提过,是特地为了给普通迭代器构造出const迭代器准备的,当然当迭代器类是实例化成普通迭代器时,此时就是一个拷贝构造,如果是构造const迭代器,那就是一个特殊的构造函数,不过此时构造函数的参数it所在的就是在const_iterator的类域内,就不能访问私有,所以说_node不要用private修饰,就是因为只有同一类域才能访问私有,切记切记,时至今日我才理解这句话的含义。
- template<class T,class Ptr,class Ref>
- struct _Iterator 封装的是一个节点指针_node
- {
- typedef _Iterator
Self; 命名成self是有原因的, - 因为在实现的过程中并不是立刻考虑到要多少个模板参数,如果++,--时返回类型写成这个-Iterator
,如果模板参数变了,所有写了Iterator都要改 - 重命名为self就方便多了
-
-
- typedef _Iterator
iterator; - 这个地方就是T的大用处,因为我们set那里要用普通迭代器来构造一个const迭代器,
- 所以我们在类内要有一个iterator类型能接收普通迭代器。
-
- typedef RBTreeNode
Node; - _Iterator(Node*node)
- :_node(node)
- {
- ;
- }
- _Iterator(const iterator& it) _node不可用private修饰
- :_node(it._node)
- {
- ;
- }
-
- Self& operator--()
- {
- if (_node->_left)//如果左不为空,就访问左树最右节点
- {
- Node* cur = _node->_left;
- while (cur->_right)
- {
- cur = cur->_right;
- }
- _node = cur;
- }
- else//左为空,向上找cur和parent节点,且parent->_right==cur
- {
- Node* parent = _node->_parent;
- Node* cur = _node;
- while (parent && cur == parent->_left)
- {
- cur = parent;
- parent = parent->_parent;
- }
- _node = parent;
- }
- return *this;
- }
-
- Self& operator++()//后置++
- {
- if (_node->_right)//如果右不为空,就访问右树最左节点
- {
- Node* cur = _node->_right;
- while (cur->_left)
- {
- cur = cur->_left;
- }
- _node = cur;
- }
- else
- {
- Node* cur = _node;
- Node* parent = cur->_parent;
- while (parent&&cur == parent->_left)//可能只有一个root节点
- {
- cur = cur->_parent;
- parent = parent->_parent;
- }
- _node = parent;
- }
- return (*this);//返回迭代器,this是一个迭代器类型的指针
- }
-
- Self operator++(int)//前置++
- {
- Self ret = (*this);
- if (_node->_right)//如果右不为空,就访问右树最左节点
- {
- Node* cur = _node->_right;
- while (cur->_left)
- {
- cur = cur->_left;
- }
- _node = cur;
- }
- else
- {
- Node* cur = _node;
- Node* parent = cur->_parent;
- while (parent && cur == parent->_right)//可能只有一个root节点,
- //当cur为parent的左节点或者parent为空,循环结束
- {
- cur = cur->_parent;
- parent = parent->_parent;
- }
- _node = parent;
- }
- return ret;
- }
-
- bool operator!=(const Self&data) 比较迭代器是否不同,就看包含节点是否相等
- {
- return _node != data._node; data是迭代器类型,访问成员用.操作符
- }
-
- Ref operator*() 返回节点数据
- {
- return _node->_data;
- }
-
-
- Ptr operator->() 返回节点数据的地址
- {
- return &(_node->_data); 只能返回地址
- }
- Node* _node;
- };
总算写完了,一开始觉得这篇难以下手,写了一两天解决了一个又一个小问题后发现困难越来越小,有时候一件事难度太大,分成一件件小事一件件完成或许成功概率更大。