一、前言
前面我们已经模拟实现和封装了map和set这两个容器,今天我们来模拟封装一下unordered系列的两个容器,我们前面已经对unordered系列的两个容器有所了解,它们的底层使用的是哈希桶来实现的,前面的文章已经讲过哈希桶的实现了。下面我们和实现map和set两个容器的方法一样依然分步骤进行实现。
二、基本架构的实现
unordered_map的基本架构
namespace Code_Journey
{
struct MapKeyOfT
{
template<class K, class V>
const K& operator()(const pair<K, V>& kv) const
{
return kv.first;
}
};
template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
typedef HashTable<K, pair<K, V>, MapKeyOfT, Hash> ht;
public:
bool insert(const pair<K, V>& kv)
{
return _ht.insert(kv);
}
private:
ht _ht;
};
}
unordered_set的基本架构
namespace Code_Journey
{
struct SetKeyOfT
{
template<class K>
const K& operator()(const K& key) const
{
return key;
}
};
template<class K, class Hash = HashFunc<K>>
class unordered_set
{
typedef HashTable<K, K, SetKeyOfT, Hash> ht;
public:
bool insert(const K& key)
{
return _ht.insert(key);
}
private:
ht _ht;
};
}
三、实现普通迭代器
这里实现普通迭代器要注意下面几个点:
第一点:两个类相互依赖的问题,因为实现迭代器要用到HashTable这个类,所以造成了两个类相互依赖的问题,如果想要解决这个问题就需要提前在迭代器实现前声明一下HashTable这个类,告诉编译器我已经实现了这个类。
第二点:operator++的实现逻辑,实现operator++的主要在于我们要分清楚他的几种情况,第一种情况就是当前的哈希桶还没有遍历完,这种情况最简单,只需要我们将_node更新一下即可,第二种情况就是这个哈希桶遍历完成,需要遍历下一个哈希桶,这种情况需要借助HashTable这个类,然后找到下一个桶的第一个位置直接更新给_node就可以了,这里需要注意的一点是一定要从下一个桶开始找起,否则就会陷入无限循环,就是将hashi++之后在找。进入无限循环的原因就是当hashi这个桶内如果只有它自己一个元素就会陷入无线打印该元素。因为我们计算出的hashi这个地址一定是不为空的,也就是说每次调用这个++都会打印这个hashi这个位置的值。第三种是最简单的就是接下来是空,并且后续没有桶了,这种情况直接给_node赋值为nullptr即可。
第三点:设置友元类,由于我们实现的operator++内使用了HashTable的私有成员,所以我们要将我们的迭代器设置成HashTable的友元类,记住将模板设计成友元的时候要带全模板参数。
// 声明HashTable模板
template<class K, class T, class KeyOfT, class Hash>
class HashTable;
template<class K, class T, class KeyOfT, class Hash>
struct HashTabel_Iterator
{
typedef HashTable<K, T, KeyOfT, Hash> HT;
typedef HashTabel_Iterator<K, T, KeyOfT, Hash> Self;
typedef HashTableNode<T> Node;
Node* _node;
HT* _ht;
HashTabel_Iterator(Node* node, HT* ht)
:_node(node)
,_ht(ht)
{}
T& operator*()
{
return _node->_data;
}
T* operator->()
{
return &_node->_data;
}
bool operator==(const Self& it) const
{
return _node == it._node;
}
bool operator!=(const Self& it) const
{
return _node != it._node;
}
Self& operator++()
{
Hash hs;
KeyOfT kot;
if (_node->_next)
{
_node = _node->_next;
return *this;
}
size_t hashi = hs(kot(_node->_data)) % _ht->_table.size();
// 这里一定要注意先对hashi进行++操作,否则会进行无限循环
// 因为hashi这个位置一定是cur存在的位置
hashi++;
for (size_t i = hashi; i < _ht->_table.size(); i++)
{
Node* cur = _ht->_table[i];
if (cur)
{
_node = cur;
return *this;
}
}
_node = nullptr;
return *this;
}
};
四、实现const迭代器
在实现const迭代器的时候很有可能会遇到需要乱七八糟的错误,出现这些错误的原因多半都是出自模板的报错。之前也说过模板的报错一报就是一长串,这种报错会很让人崩溃,这也是为什么我封装东西总是一步一步的来,这样可以极大的减少报错的概率,就算出现了许多乱七八糟的错误也知道是那一步出现的错误,时刻要记住满就是快,一步一步的来,不可能一口吃成胖子,当代码能力成熟了再尝试去跳步。
下面来说一下封装const迭代器时遇到的错误:
- 迭代器成员_ht必须定义成const类型,因为在实现const迭代器的时候this指针是经过const修饰的,如果这里不加const就无法构建const迭代器。
- 封装const迭代器的时候会增加模板参数,一定要按照顺序一步一步的去增加,否则就会报出乱七八糟的错误。
- 一定要记住普通对象只能调用普通的迭代器,只有const对象才能调用const迭代器。
template<class K, class T, class KeyOfT, class Hash, class Ref, class Ptr>
struct HashTable_Iterator
{
typedef HashTable<K, T, KeyOfT, Hash> HT;
typedef HashTable_Iterator<K, T, KeyOfT, Hash, Ref, Ptr> Self;
typedef HashTableNode<T> Node;
Node* _node;
const HT* _ht;
HashTable_Iterator(Node* node, const HT* ht)
:_node(node)
,_ht(ht)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator==(const Self& it) const
{
return _node == it._node;
}
bool operator!=(const Self& it) const
{
return _node != it._node;
}
Self& operator++()
{
Hash hs;
KeyOfT kot;
if (_node->_next)
{
_node = _node->_next;
return *this;
}
size_t hashi = hs(kot(_node->_data)) % _ht->_table.size();
// 这里一定要注意先对hashi进行++操作,否则会进行无限循环
// 因为hashi这个位置一定是cur存在的位置
hashi++;
for (size_t i = hashi; i < _ht->_table.size(); i++)
{
Node* cur = _ht->_table[i];
if (cur)
{
_node = cur;
return *this;
}
}
_node = nullptr;
return *this;
}
};
五、重载[]运算符
下面我们来实现一下unordered_map的operator[]。重载[]依然是借助insert成员函数,insert在重载[]时起到的时查找和插入的功能。所以我们要先将insert成员函数的返回值改成pair<iterator,bool>然后我们用它返回的迭代器取结点中的pair成员的second成员。
V& operator[](const K& key)
{
pair<iterator, bool> ret = _ht.insert({key, V()});
return ret.first->second;
}
六、封装后的代码
unordered_map封装后的代码
#pragma once
#include"HashTable.h"
namespace Code_Journey
{
struct MapKeyOfT
{
template<class K, class V>
const K& operator()(const pair<K, V>& kv) const
{
return kv.first;
}
};
template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
typedef HashTable<K, pair<K, V>, MapKeyOfT, Hash> ht;
public:
typedef typename HashTable<K, pair<K, V>, MapKeyOfT, Hash>::Iterator iterator;
typedef typename HashTable<K, pair<K, V>, MapKeyOfT, Hash>::ConstIterator const_iterator;
iterator begin()
{
return _ht.Begin();
}
iterator end()
{
return _ht.End();
}
const_iterator begin()const
{
return _ht.Begin();
}
const_iterator end()const
{
return _ht.End();
}
pair<iterator, bool> insert(const pair<K, V>& kv)
{
return _ht.insert(kv);
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = _ht.insert({key, V()});
return ret.first->second;
}
iterator find(const K& key)
{
return _ht.find(key);
}
bool erase(const K& key)
{
return _ht.erase(key);
}
private:
ht _ht;
};
}
unordered_set封装后的代码
#pragma once
#include"HashTable.h"
namespace Code_Journey
{
struct SetKeyOfT
{
template<class K>
const K& operator()(const K& key) const
{
return key;
}
};
template<class K, class Hash = HashFunc<K>>
class unordered_set
{
typedef HashTable<K, K, SetKeyOfT, Hash> ht;
public:
typedef typename HashTable<K, K, SetKeyOfT, Hash>::Iterator iterator;
typedef typename HashTable<K, K, SetKeyOfT, Hash>::ConstIterator const_iterator;
iterator begin()
{
return _ht.Begin();
}
iterator end()
{
return _ht.End();
}
const_iterator begin()const
{
return _ht.Begin();
}
const_iterator end()const
{
return _ht.End();
}
pair<iterator, bool> insert(const K& key)
{
return _ht.insert(key);
}
iterator find(const K& key)
{
return _ht.find(key);
}
bool erase(const K& key)
{
return _ht.erase(key);
}
private:
ht _ht;
};
}