目录
前言
二、unordered_set的封装
1.模板参数列表的改造
2. 增加迭代器操作
3. 模板参数的意义
三、unordered_map的封装
1、“轮子所需要的参数
2、迭代器
四、完整代码
1、HashTable
2、unordered_set
3、unordered_map
总结
前言
unordered_set和map的介绍在上一篇博客有所提到
C++——深部解析哈希
在编程的世界里,数据结构是构建高效、可扩展程序的基石。C++作为一门功能强大的编程语言,提供了丰富的标准库容器,其中unordered_set和map以其高效的查找和插入操作而广受欢迎。然而,尽管这些容器功能强大,直接使用它们有时可能不够直观或灵活,特别是在需要特定行为或扩展功能时。因此,封装这些容器,以提供更符合特定需求的接口,成为了一种常见的编程实践。
本博客旨在深入探讨如何封装C++中的unordered_set和map,以创建更加灵活、易用的数据结构。通过实例和代码演示,你将学会如何扩展这些容器的功能,以满足项目中的特定需求,同时保持高效性和可维护性。
一、为什么要封装?
提升编程效率:封装unordered_set和map可以简化代码,减少重复劳动。通过创建通用的、可重用的封装,你可以在多个项目中快速部署这些数据结构,而无需每次都从头开始实现。
增强代码可读性:封装后的容器往往具有更清晰的接口和更直观的命名,这使得代码更易于理解和维护。对于团队项目来说,这尤其重要,因为它可以降低新成员理解代码的难度。
优化性能:在某些情况下,直接使用标准库的容器可能不是最优解。通过封装,你可以根据具体需求定制容器的行为,比如调整哈希函数、比较器或添加缓存机制,从而提升性能。
扩展功能:标准库的容器提供了基础功能,但可能无法满足所有需求。封装允许你添加额外的功能,如持久化、线程安全、事件监听等,使容器更加适应复杂的应用场景。
二、unordered_set的封装
1.模板参数列表的改造
代码如下(示例):
// K:关键码类型
// V: 不同容器V的类型不同,如果是unordered_map,V代表一个键值对,如果是
unordered_set,V 为 K
// KeyOfValue: 因为V的类型不同,通过value取key的方式就不同,详细见
unordered_map/set的实现
// HF: 哈希函数仿函数对象类型,哈希函数使用除留余数法,需要将Key转换为整形数字才能
取模
template<class K, class V, class KeyOfValue, class Hash = HashFunc<K> >
class HashBucket;
template<class K, class Hash = HashFunc<K>>
class unordered_set
{
public:
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
private:
HashBucket::HashTable<K, K, SetKeyOfT, Hash> _ht;
};
SetKeyOfT是用来获取k的key,用于比较;
2. 增加迭代器操作
代码如下(示例):
// 前置声明
template<class K, class T, class KeyOfT, class Hash>
class HashTable;
template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
struct __HashIterator
{
typedef HashNode<T> Node;
typedef HashTable<K, T, KeyOfT, Hash> HT;
typedef __HashIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;
typedef __HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
Node* _node;
const HT* _ht;
__HashIterator(Node* node, const HT* ht)
:_node(node)
, _ht(ht)
{}
__HashIterator(const Iterator& it)
:_node(it._node)
, _ht(it._ht)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
// ++it 17:05继续
Self& operator++()
{
if (_node->_next != nullptr)
{
_node = _node->_next;
}
else
{
// 找下一个不为空的桶
KeyOfT kot;
Hash hash;
// 算出我当前的桶位置
size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
++hashi;
while (hashi < _ht->_tables.size())
{
if (_ht->_tables[hashi])
{
_node = _ht->_tables[hashi];
break;
}
else
{
++hashi;
}
}
// 没有找到不为空的桶
if (hashi == _ht->_tables.size())
{
_node = nullptr;
}
}
return *this;
}
};
3. 模板参数的意义
我们的Hash模板参数是用来获取key的类型将其转换为整型,方便与在顺序表里找桶的位置;因为string类型的不像整型一样可以直接找到顺序表里放桶的位置;
KeyOfT用来将指针所指向的数据转换为key类型。
三、unordered_map的封装
1、“轮子所需要的参数
template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
public:
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
typedef typename HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;
typedef typename HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::const_iterator const_iterator;
private:
HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};
因为我们在封装时已经将大部分的模板参数给封装好了,只需要改变MapKeyOfT的()重载即可;
2、迭代器
可以直接复用set封装时实现的;
四、完整代码
1、HashTable
template<>
struct HashFunc<string>
{
// BKDR
size_t operator()(const string& s)
{
size_t hash = 0;
for (auto ch : s)
{
hash += ch;
hash *= 31;
}
return hash;
}
};
namespace HashBucket
{
template<class T>
struct HashNode
{
HashNode<T>* _next;
T _data;
HashNode(const T& data)
:_next(nullptr)
, _data(data)
{}
};
// 前置声明
template<class K, class T, class KeyOfT, class Hash>
class HashTable;
template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
struct __HashIterator
{
typedef HashNode<T> Node;
typedef HashTable<K, T, KeyOfT, Hash> HT;
typedef __HashIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;
typedef __HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
Node* _node;
const HT* _ht;
__HashIterator(Node* node, const HT* ht)
:_node(node)
, _ht(ht)
{}
__HashIterator(const Iterator& it)
:_node(it._node)
, _ht(it._ht)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
// ++it 17:05继续
Self& operator++()
{
if (_node->_next != nullptr)
{
_node = _node->_next;
}
else
{
// 找下一个不为空的桶
KeyOfT kot;
Hash hash;
// 算出我当前的桶位置
size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
++hashi;
while (hashi < _ht->_tables.size())
{
if (_ht->_tables[hashi])
{
_node = _ht->_tables[hashi];
break;
}
else
{
++hashi;
}
}
// 没有找到不为空的桶
if (hashi == _ht->_tables.size())
{
_node = nullptr;
}
}
return *this;
}
};
template<class K, class T, class KeyOfT, class Hash>
class HashTable
{
template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
friend struct __HashIterator;
typedef HashNode<T> Node;
public:
typedef __HashIterator<K, T, T&, T*, KeyOfT, Hash> iterator;
typedef __HashIterator<K, T, const T&, const T*, KeyOfT, Hash> const_iterator;
iterator begin()
{
Node* cur = nullptr;
for (size_t i = 0; i < _tables.size(); ++i)
{
cur = _tables[i];
if (cur)
{
break;
}
}
return iterator(cur, this);
}
iterator end()
{
return iterator(nullptr, this);
}
const_iterator begin() const
{
Node* cur = nullptr;
for (size_t i = 0; i < _tables.size(); ++i)
{
cur = _tables[i];
if (cur)
{
break;
}
}
return const_iterator(cur, this);
}
const_iterator end() const
{
return const_iterator(nullptr, this);
}
~HashTable()
{
for (auto& cur : _tables)
{
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
cur = nullptr;
}
}
iterator Find(const K& key)
{
if (_tables.size() == 0)
return end();
KeyOfT kot;
Hash hash;
size_t hashi = hash(key) % _tables.size();
Node* cur = _tables[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
return iterator(cur, this);
}
cur = cur->_next;
}
return end();
}
bool Erase(const K& key)
{
Hash hash;
KeyOfT kot;
size_t hashi = hash(key) % _tables.size();
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
if (prev == nullptr)
{
_tables[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return false;
}
// size_t newsize = GetNextPrime(_tables.size());
size_t GetNextPrime(size_t prime)
{
// SGI
static const int __stl_num_primes = 28;
static const unsigned long __stl_prime_list[__stl_num_primes] =
{
53, 97, 193, 389, 769,
1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433,
1572869, 3145739, 6291469, 12582917, 25165843,
50331653, 100663319, 201326611, 402653189, 805306457,
1610612741, 3221225473, 4294967291
};
size_t i = 0;
for (; i < __stl_num_primes; ++i)
{
if (__stl_prime_list[i] > prime)
return __stl_prime_list[i];
}
return __stl_prime_list[i];
}
pair<iterator, bool> Insert(const T& data)
{
KeyOfT kot;
iterator it = Find(kot(data));
if (it != end())
{
return make_pair(it, false);
}
Hash hash;
// 负载因因子==1时扩容
if (_n == _tables.size())
{
/*size_t newsize = _tables.size() == 0 ? 10 : _tables.size()*2;
HashTable<K, V> newht;
newht.resize(newsize);
for (auto cur : _tables)
{
while (cur)
{
newht.Insert(cur->_kv);
cur = cur->_next;
}
}
_tables.swap(newht._tables);*/
//size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
size_t newsize = GetNextPrime(_tables.size());
vector<Node*> newtables(newsize, nullptr);
//for (Node*& cur : _tables)
for (auto& cur : _tables)
{
while (cur)
{
Node* next = cur->_next;
size_t hashi = hash(kot(cur->_data)) % newtables.size();
// 头插到新表
cur->_next = newtables[hashi];
newtables[hashi] = cur;
cur = next;
}
}
_tables.swap(newtables);
}
size_t hashi = hash(kot(data)) % _tables.size();
// 头插
Node* newnode = new Node(data);
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
++_n;
return make_pair(iterator(newnode, this), false);;
}
size_t MaxBucketSize()
{
size_t max = 0;
for (size_t i = 0; i < _tables.size(); ++i)
{
auto cur = _tables[i];
size_t size = 0;
while (cur)
{
++size;
cur = cur->_next;
}
//printf("[%d]->%d\n", i, size);
if (size > max)
{
max = size;
}
}
return max;
}
private:
vector<Node*> _tables; // 指针数组
size_t _n = 0; // 存储有效数据个数
};
}
2、unordered_set
#pragma once
#include"HashTable.h"
namespace my_unordered_set
{
template<class K, class Hash = HashFunc<K>>
class unordered_set
{
public:
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename HashBucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator iterator;
typedef typename HashBucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator 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:
HashBucket::HashTable<K, K, SetKeyOfT, Hash> _ht;
};
void print(const unordered_set<int>& s)
{
unordered_set<int>::const_iterator it = s.begin();
while (it != s.end())
{
//*it = 1;
cout << *it << " ";
++it;
}
cout << endl;
}
void test_unordered_set1()
{
int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
unordered_set<int> s;
for (auto e : a)
{
s.insert(e);
}
s.insert(54);
s.insert(107);
unordered_set<int>::iterator it = s.begin();
while (it != s.end())
{
//*it = 1;
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
print(s);
}
}
3、unordered_map
#pragma once
#include "HashTable.h"
namespace my_unordered_map
{
template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
public:
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
typedef typename HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;
typedef typename HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::const_iterator 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 = insert(make_pair(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:
HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};
}
总结
封装在于对模板和迭代器的底层逻辑和重写