C++进阶(unordered_set+unordered_map模拟实现)
unordered_set
- unordered_set是以无特定顺序存储唯一元素的容器,并且允许根据它们的值快速检索单个元素,是一种K模型。
- 在unordered_set中,元素的值同时是它的key,它唯一地标识它。键值是不可变的,因unordered_set中的元素不能在容器中修改一次 ,但是可以插入和删除它们。
- 在内部,unordered_set中的元素不是按任何特定顺序排序的(无序),而是根据它们的哈希值组织成桶,以允许直接通过它们的值快速访问单个元素,时间复杂度可以达到O(1)。
- unordered_set容器比set容器更快地通过它们的key访问单个元素,尽管它们通常对于通过其元素的子集进行范围迭代的效率较低。
- 容器中的迭代器至少是单向迭代器。
使用
构造函数
unordered_set<string> uset;//构造函数
unordered_set<string> uset2(++uset.begin(),uset.end());//,如果不想全部拷贝,可以使用 unordered_set 类模板提供的迭代器,在现有 unordered_set 容器中选择部分区域内的元素,为新建 unordered_set 容器初始化
unordered_set ( const unordered_set& ust );//拷贝构造
容量和大小
empty();//若容器为空,则返回 true;否则 false
size();//返回当前容器中存有元素的个数
迭代器
begin();//返回指向容器中第一个元素的正向迭代器
end();//返回指向容器中最后一个元素之后位置的正向迭代器
cbegin();//和begin()功能相同,只不过其返回的是const类型的正向迭代器
cend();//和end()功能相同,只不过其返回的是const类型的正向迭代器
元素的访问和查找
find(key);//查找以值为 key 的元素,如果找到,则返回一个指向该元素的正向迭代器;反之,则返回一个指向容器中最后一个元素之后位置的迭代器(如果end()方法返回的迭代器)
count(key);//在容器中查找值为 key 的元素的个数
equal_range(key);//返回一个pair对象,其包含2个迭代器,用于表明当前容器中值为key的元素所在的范围
元素的插入和删除
emplace();//向容器中添加新元素,效率比insert()方法高
emplace_hint();//向容器中添加新元素,效率比 nsert()方法高
insert();//向容器中添加新元素
erase();//删除指定元素
clear();//清空容器,即删除容器中存储的所有元素
swap();//交换2个 unordered_set 容器存储的元素,前提是必须保证这 2 个容器的类型完全相等
实例演示:
void test_unordered_set()
{
unordered_set<int> us;
set<int> s;
int arr[] = { 4,2,3,1,6,8,9,3 };
for (auto e : arr)
{
us.insert(e);
s.insert(e);
}
unordered_set<int>::iterator usit = us.begin();
set<int>::iterator sit = s.begin();
cout << "unordered_set:" << endl;
while (usit != us.end())
{
cout << *usit << " ";
++usit;
}
cout << endl;
cout << "set:" << endl;
while (sit != s.end())
{
cout << *sit << " ";
++sit;
}
cout << endl;
}
unordered_map
介绍
- unordered_map是存储<key, value>键值对(KV模型)的关联式容器,其允许通过keys快速的索引到与其对应的value。
- 在unordered_map中,键值通常用于唯一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
- 在内部,unordered_map没有对<key, value>按照任何特定的顺序排序(无序), 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
- unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
- unordered_map实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。
- 它的迭代器是一个单向迭代器。
使用
构造函数
unordered_map<string,string> umap//构造函数: 可以不初始化地构造,也可以用一个容器的迭代器去构造
unordered_map ( const unordered_map& ump );//拷贝构造
容量和大小
empty();//判断容器是否为空,,若容器为空,则返回 true;否则 false
size();//返回容器中的元素个数
迭代器
begin();//返回指向容器中第一个键值对的正向迭代器
end();//返回指向容器中最后一个键值对之后位置的正向迭代器
cbegin();//和 begin() 功能相同,只不过在其基础上增加了 const 属性,即该方法返回的迭代器不能用于修改容器内存储的键值对
cend();//和 end() 功能相同,只不过在其基础上,增加了 const 属性,即该方法返回的迭代器不能用于修改容器内存储的键值对
元素的访问和查找
operator[key];//该模板类中重载了 [] 运算符,其功能是可以向访问数组中元素那样,只要给定某个键值对的键 key,就可以获取该键对应的值。注意,如果当前容器中没有以 key 为键的键值对,则其会使用该键向当前容器中插入一个新键值对
at(key);//返回容器中存储的键 key 对应的值,如果key不存在,则会抛出 out_of_range 异常
find(key);//查找以key为键的键值对,如果找到,则返回一个指向该键值对的正向迭代器;反之,则返回一个指向容器中最后一个键值对之后位置的迭代器(如果end()方法返回的迭代器)
元素的插入和删除
emplace();//向容器中添加新键值对,效率比 insert()方法高
emplace_hint();//向容器中添加新键值对,效率比insert()方法高
insert();//向容器中添加新键值对
erase();//删除指定键值对
clear();//清空容器,即删除容器中存储的所有键值对
swap();//交换2个unordered_map容器存储的键值对,前提是必须保证这2个容器的类型完全相等
void test_unordered_map()
{
unordered_map<int, int> um;
map<int, int> m;
int arr[] = { 4,2,3,1,6,8,9,3 };
for (auto e : arr)
{
um.insert(make_pair(e, e));
m.insert(make_pair(e, e));
}
unordered_map<int, int>::iterator umit = um.begin();
map<int, int>::iterator mit = m.begin();
cout << "unordered_map:" << endl;
while (umit != um.end())
{
cout << umit->first << ":" << umit->second << endl;
++umit;
}
cout << "map:" << endl;
while (mit != m.end())
{
cout << mit->first << ":" << mit->second << endl;
++mit;
}
}
unordered_map和unordered_set的实现
整体概述
这里我们用上一篇的博客中的哈希桶来封装出unordered_map和unordered_set两个容器
哈希桶代码:HashTable.h文件
#pragma once
#include<iostream>
#include<vector>
using namespace std;
const int PRIMECOUNT = 28;
const size_t primeList[PRIMECOUNT] =
{
53ul, 97ul, 193ul, 389ul, 769ul,
1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
1610612741ul, 3221225473ul, 4294967291ul
};
//仿函数,获取key值
template<class K, class V>
struct KeyOfValue
{
const K& operator()(const K& key)
{
return key;
}
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
namespace CLOSE_HASH
{
//闭散列,需要三种状态
enum State
{
EMPTY,
EXITS,
DELETE
};
//定义节点
template <class T>
struct HashData
{
//这里的data对象是匿名对象转化的,被const修饰不能修改,就是传参的关系,你传入了一个对象
//被const修饰,就代表传入的那个对象不能被修改,你自己的内容还是可以修改的
HashData(const T& data = T(), const State& state = EMPTY)
: data(data)
, state(state){}
T data;
State state;
};
//哈希表
template<class K, class T, class KOFV>
class HashTable
{
typedef HashData<T> HashData;
public:
HashTable(size_t capacity = 10)
: table(capacity)
, size(0)
{}
size_t getNextPrime(size_t num)
{
size_t i = 0;
for (i = 0; i < PRIMECOUNT; i++)
{
//返回比那个数大的下一个质数
if (primeList[i] > num)
{
return primeList[i];
}
}
//如果比所有都大,还是返回最后一个,因为最后一个已经是32位最大容量
return primeList[PRIMECOUNT - 1];
}
//除留余数法
size_t HashFunc(const K& key)
{
return key % table.size();
}
//插入元素
bool Insert(const T& data)
{
/*
1.首先要判断是否需要增容,当装填因子>0.7的时候增容(装填因子 = 数据个数/哈希表大小)
2.创建一个新表,把旧表的元素重新放到新表当中,因为表的大小发生变化,所以数据在旧表中的位置和新表的位置不一样,需要重新调整
3.利用swap将两个表进行交换,函数结束的时候,旧表被自动析构
4.增容之后,插入元素,采用线性探测,插入元素
*/
KOFV kofv;
if (size * 10 / table.size() >= 7)//增容
{
//增容的大小按照别人算好的近似两倍的素数来增,这样效率更高,也可以直接2倍或者1.5倍。
//使用了vector默认的有参构造函数vector(size_type n, const value_type& val = value_type())//有参构造用n个val构造并初始化容器
//const value_type& val = value_type()这段代码是匿名对象类型转换
vector<HashData> newTable(getNextPrime(size));
for (size_t i = 0; i < table.size(); i++)
{
//将旧表中的元素映射到新表当中
if (table[i].state == EXITS)
{
int index = HashFunc(kofv(table[i].data));
while (newTable[index].state == EXITS)
{
//不可能存在重复元素,因为旧表中不可能有重复元素
index++;
if (index == newTable.capacity())
{
index = 0;
}
}
newTable[index]=table[i];
}
}
table.swap(newTable);//交换两个表
}
//用哈希函数计算出映射的位置
size_t index = HashFunc(kofv(data));
//int start = index;
//int i = 1;
while (table[index].state == EXITS)
{
if (table[index].data == data)
return false;
// 二次探测
/*index = start + pow(i, 2);
index %= _tables.size();
++i;*/
++index;
// 走到末尾置0
if (index == table.size())
index = 0;
}
// DELETE和EMPTY的位置都可以插入数据
table[index].data = data;
table[index].state = EXITS;
++size;
return true;
}
//查找元素
HashData* Find(const K& key)
{
KOFV kofv;
size_t index = HashFunc(key);
int start = index;
while (table[index].state != EMPTY)
{
if (kofv(table[index].data) == key)
{
if (table[index].state == EXITS)
{
return &table[index];
}
// table[index].state == DELETE
else
{
//表示你要找的元素已经被删除了
return nullptr;
}
}
++index;
if (index == table.size())
index = 0;
// 找完一遍没有就退出 这里其实是不必要的,这里面一定有空的位置,所以一定会退出
if (index == start)
{
return nullptr;
}
}
return nullptr;
}
bool Erase(const K& key)
{
HashData* ret = Find(key);
//找到了,进行删除
if (ret != nullptr)
{
ret->state = DELETE;
size--;
return true;
}
else
{
//没找到
return false;
}
}
private:
vector<HashData> table;
int size = 0;
};
void TestHashTable1()
{
HashTable<int, int, KeyOfValue<int, int>> ht;
// HashTable<int, pair<int, int>, KeyOfValue<int, int>> ht;
int arr[] = { 10,20,14,57,26,30,49,72,43,55,82 };
for (auto e : arr)
{
if (e == 72)
{
int a = 0;
}
ht.Insert(e);
}
for (auto e : arr)
{
ht.Erase(e);
}
}
void TestHashTable2()
{
HashTable<int, pair<int, int>, KeyOfValue<int, int>> ht;
int arr[] = { 15,23,57,42,82,26,30,49,72,43,55 };
for (auto e : arr)
{
ht.Insert(make_pair(e, e));
}
/*for (auto e : arr)
{
ht.Erase(e);
}*/
}
}
namespace OPEN_HASH
{
template<class T>
struct HashNode
{
HashNode(const T&data):data(data),next(nullptr){}
T data;
HashNode<T>* next;
};
template<class K>
struct _Hash
{
// 大多树的类型就是是什么类型就返回什么类型
const K& operator()(const K& key)
{
return key;
}
};
// 特化string
template<>
struct _Hash<string>
{
size_t operator()(const string& key)
{
size_t hash = 0;
// 把字符串的所有字母加起来 hash = hash*131 + key[i]
for (size_t i = 0; i < key.size(); ++i)
{
hash *= 131;
hash += key[i];
}
return hash;
}
};
//前置声明
template<class K, class T, class KOFV, class Hash = _Hash<K>>
class HashBucket;
//迭代器的实现
template<class K, class T, class Ref, class Ptr, class KOFV, class Hash>
struct HashBucket_Iterator
{
typedef HashBucket_Iterator<K, T, Ref, Ptr, KOFV, Hash> Self;
typedef HashNode<T> Node;
typedef HashBucket<K, T, KOFV, Hash> HashBucket;
Node* node;
HashBucket* _phb;
HashBucket_Iterator(Node *node, HashBucket* phb):node(node),_phb(phb){}
//操作符重载
Ref operator*()
{
return node->data;
}
Ptr operator->()
{
return &node->data;
}
Self& operator++()
{
if (node->next)
{
node = node->next;
return *this;
}
else
{
KOFV kofv;
size_t index = _phb->HashFunc(kofv(node->data));
for (size_t i = index + 1; i < _phb->tables.size(); ++i)
{
if (_phb->tables[i])
{
node = _phb->tables[i];
return *this;
}
}
node = nullptr;
return *this;
}
}
bool operator==(const Self& self) const
{
return node == self.node
&& _phb == self._phb;
}
bool operator!=(const Self& self) const
{
return !this->operator==(self);
}
};
template<class K, class T, class KOFV, class Hash>
class HashBucket
{
typedef HashNode<T> Node;
friend struct HashBucket_Iterator<K, T, T&, T*, KOFV, Hash>;
public:
typedef HashBucket_Iterator<K, T, T&, T*, KOFV, Hash> iterator;
size_t getNextPrime(size_t num)
{
size_t i = 0;
for (i = 0; i < PRIMECOUNT; i++)
{
//返回比那个数大的下一个质数
if (primeList[i] > num)
{
return primeList[i];
}
}
//如果比所有都大,还是返回最后一个,因为最后一个已经是32位最大容量
return primeList[PRIMECOUNT - 1];
}
//除留余数法
size_t HashFunc(const K& key)
{
Hash hash;
return hash(key) % tables.size();
}
iterator begin()
{
for (size_t i = 0; i < tables.size(); ++i)
{
if (tables[i] != nullptr)
return iterator(tables[i], this);// 哈希桶的第一个节点
}
return end();// 没有节点返回最后一个迭代器
}
iterator end()
{
return iterator(nullptr, this);
}
~HashBucket()
{
Clear();
}
void Clear()
{
for (size_t i = 0; i < tables.size(); ++i)
{
Node* cur = tables[i];
while (cur)
{
Node* next = cur->next;
delete cur;
cur = next;
}
}
}
pair<iterator, bool> Insert(const T& data)
{
KOFV kofv;
//负载因子为1就增容
if (size == tables.size())
{
vector<Node*> newTable(getNextPrime(size));
for (size_t i = 0; i < tables.size(); i++)
{
Node* prev = nullptr;
Node* cur = tables[i];
//把一个位置的所有节点转移,然后换下一个位置
while (cur)
{
//记录下一个节点的位置
Node* next = cur->next;
size_t index = HashFunc(kofv(cur->data));
//把cur连接到新表上
cur->next = newTable[index];
newTable[index] = cur;
//cur会发生变化,需要提前记录next
cur = next;
}
}
tables.swap(newTable);
}
size_t index = HashFunc(kofv(data));
// 先查找该条链表上是否有要插入的元素
Node* cur = tables[index];
while (cur)
{
if (kofv(cur->data) == kofv(data))
return make_pair(iterator(cur, this), false);
cur = cur->next;
}
// 插入数据,选择头插(也可以尾插)
Node* newnode = new Node(data);
newnode->next = tables[index];
tables[index] = newnode;
++size;
return make_pair(iterator(newnode, this), true);
}
iterator Find(const K& key)
{
KOFV kofv;
int index = HashFunc(key) ;
Node* cur = tables[index];
while (cur)
{
if (key == kofv(cur->data))
{
return iterator(cur, this);
}
cur = cur->next;
}
return iterator(nullptr);
}
bool Erase(const K& key)
{
KOFV kofv;
int index = HashFunc(key) ;
Node* prev = nullptr;
Node* cur = tables[index];
while (cur)
{
if (key == kofv(cur->data))
{
// 删第一个节点时
if (prev == nullptr)
{
tables[index] = cur->next;
}
else
{
prev->next = cur->next;
}
size--;
delete cur;
return true;
}
prev = cur;
cur = cur->next;
}
return false;
}
private:
vector<Node*> tables;
int size = 0;
};
void TestHashBucket2()
{
HashBucket<int, int, KeyOfValue<int, int>> ht;
int arr[] = { 15,23,57,42,82,26,30,49,72,43,55 };
for (auto e : arr)
{
ht.Insert(e);
}
for (auto e : arr)
{
HashBucket<int, int, KeyOfValue<int, int>>::iterator it = ht.begin();
while (it != ht.end())
{
cout << *it << " ";
++it;
}
cout << endl;
ht.Erase(e);
}
}
void TestHashBucket3()
{
HashBucket<string, string, KeyOfValue<string, string>> ht;
ht.Insert("solleHas");
ht.Insert("apple");
ht.Insert("sort");
ht.Insert("pass");
ht.Insert("cet6");
HashBucket<string, string, KeyOfValue<string, string>>::iterator it = ht.begin();
while (it != ht.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
}
改造哈希表
整体框架
这里用的是哈希桶来封装unordered_map和unordered_set两个容器
const int PRIMECOUNT = 28;
const size_t primeList[PRIMECOUNT] =
{
53ul, 97ul, 193ul, 389ul, 769ul,
1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
1610612741ul, 3221225473ul, 4294967291ul
};
template<class T>
struct HashNode
{
HashNode(const T&data):data(data),next(nullptr){}
T data;
HashNode<T>* next;
};
template<class K>
struct _Hash
{
// 大多树的类型就是是什么类型就返回什么类型
const K& operator()(const K& key)
{
return key;
}
};
// 特化string
template<>
struct _Hash<string>
{
size_t operator()(const string& key)
{
size_t hash = 0;
// 把字符串的所有字母加起来 hash = hash*131 + key[i]
for (size_t i = 0; i < key.size(); ++i)
{
hash *= 131;
hash += key[i];
}
return hash;
}
};
template<class K, class T, class KOFV, class Hash>
class HashBucket
{
private:
vector<Node*> tables;
int size = 0;// 记录表中的数据个数
};
为了让哈希表能够跑起来,我们这里实现一个迭代器的操作
迭代器的框架: 这里有两个成员,一个是节点指针,还有一个是哈希表的指针,只要就是为了方便实现迭代器++遍历哈希表的操作。模板参数列表的前四个主要是为了实现普通迭代器和const迭代器,第五个参数就是为了获得T中的key值,是一个仿函数(前几篇博客都有提到过),最后一个模板参数是哈希函数,为了构造出哈希表指针而存在
template<class K, class T, class Ref, class Ptr, class KOFV, class Hash>
struct HashBucket_Iterator
{
typedef HashBucket_Iterator<K, T, Ref, Ptr, KOFV, Hash> Self;
typedef HashNode<T> Node;
typedef HashBucket<K, T, KOFV, Hash> HashBucket;
Node* node;
HashBucket* _phb;
HashBucket_Iterator(Node *node, HashBucket* phb):node(node),_phb(phb){}
}
迭代器基本操作的实现:
//迭代器的实现
template<class K, class T, class Ref, class Ptr, class KOFV, class Hash>
struct HashBucket_Iterator
{
typedef HashBucket_Iterator<K, T, Ref, Ptr, KOFV, Hash> Self;
typedef HashNode<T> Node;
typedef HashBucket<K, T, KOFV, Hash> HashBucket;
Node* node;
HashBucket* _phb;
HashBucket_Iterator(Node *node, HashBucket* phb):node(node),_phb(phb){}
//操作符重载
Ref operator*()
{
return node->data;
}
Ptr operator->()
{
return &node->data;
}
Self& operator++()
{
if (node->next)
{
node = node->next;
return *this;
}
else
{
KOFV kofv;
size_t index = _phb->HashFunc(kofv(node->data));
for (size_t i = index + 1; i < _phb->tables.size(); ++i)
{
if (_phb->tables[i])
{
node = _phb->tables[i];
return *this;
}
}
node = nullptr;
return *this;
}
}
bool operator==(const Self& self) const
{
return node == self.node
&& _phb == self._phb;
}
bool operator!=(const Self& self) const
{
return !this->operator==(self);
}
};
哈希表内部改造:
template<class K, class T, class KOFV, class Hash>
class HashBucket
{
typedef HashNode<T> Node;
friend struct HashBucket_Iterator<K, T, T&, T*, KOFV, Hash>;
public:
typedef HashBucket_Iterator<K, T, T&, T*, KOFV, Hash> iterator;
size_t getNextPrime(size_t num)
{
size_t i = 0;
for (i = 0; i < PRIMECOUNT; i++)
{
//返回比那个数大的下一个质数
if (primeList[i] > num)
{
return primeList[i];
}
}
//如果比所有都大,还是返回最后一个,因为最后一个已经是32位最大容量
return primeList[PRIMECOUNT - 1];
}
//除留余数法
size_t HashFunc(const K& key)
{
Hash hash;
return hash(key) % tables.size();
}
iterator begin()
{
for (size_t i = 0; i < tables.size(); ++i)
{
if (tables[i] != nullptr)
return iterator(tables[i], this);// 哈希桶的第一个节点
}
return end();// 没有节点返回最后一个迭代器
}
iterator end()
{
return iterator(nullptr, this);
}
~HashBucket()
{
Clear();
}
void Clear()
{
for (size_t i = 0; i < tables.size(); ++i)
{
Node* cur = tables[i];
while (cur)
{
Node* next = cur->next;
delete cur;
cur = next;
}
}
}
};
封装unordered_map和unordered_set
unordered_set.h头文件
#pragma once
#include"HashTable.h"
using namespace OPEN_HASH;
namespace Simulation
{
template<class K, class Hash = _Hash<K>>
class unordered_set
{
struct SetKeyOfValue
{
const K& operator()(const K& key)
{
return key;
}
};
// 告诉编译器这只是一个类型
typedef HashBucket<K, K, SetKeyOfValue, Hash> HashBucket;
public:
// 告诉编译器这只是一个类型
typedef typename HashBucket::iterator iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
pair<iterator, bool> insert(const K& kv)
{
return _ht.Insert(kv);
}
bool erase(const K& key)
{
return _ht.Erase(key);
}
iterator find(const K& key)
{
return _ht.Find(key);
}
private:
HashBucket _ht;
};
void test_unordered_set1()
{
unordered_set<int> s;
s.insert(3);
s.insert(2);
s.insert(1);
s.insert(2);
s.insert(4);
s.erase(3);
for (auto e : s)
{
cout << e << endl;
}
}
void test_unordered_set2()
{
unordered_set<string> s;
s.insert("sort");
s.insert("pass");
s.insert("cet6");
s.insert("pass");
s.insert("cet6");
s.erase("sort");
for (auto& e : s)
{
cout << e << endl;
}
}
}
unordered_map.h头文件
#pragma once
#include"HashTable.h"
using namespace OPEN_HASH;
namespace Simulation
{
template<class K, class V, class Hash = _Hash<K>>
class unordered_map
{
struct MapKeyOfValue
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
typedef HashBucket<K, pair<K, V>, MapKeyOfValue, Hash> HashBucket;
public:
// 告诉编译器这只是一个类型
typedef typename HashBucket::iterator iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
pair<iterator, bool> insert(const pair<K, V>& kv)
{
return _ht.Insert(kv);
}
bool erase(const K& key)
{
return _ht.Erase(key);
}
iterator find(const K& key)
{
return _ht.Find(key);
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
return ret.first->second;
}
private:
HashBucket _ht;
};
void test_unordered_map1()
{
unordered_map<int, int> um;
um.insert(make_pair(1, 1));
um.insert(make_pair(3, 3));
um.insert(make_pair(2, 2));
um.insert(make_pair(4, 4));
for (auto& e : um)
{
cout << e.first << ":" << e.second << endl;
}
/*unordered_map<int, int>::iterator it = um.begin();
++it;
cout << it->first << endl;*/
}
void test_unordered_map2()
{
unordered_map<string, string> um;
um.insert(make_pair("string", "字符串"));
um.insert(make_pair("sort", "排序"));
um.insert(make_pair("pass", "通过"));
um.insert(make_pair("program", "程序"));
for (auto& e : um)
{
cout << e.first << ":" << e.second << endl;
}
/*unordered_map<int, int>::iterator it = um.begin();
++it;
cout << it->first << endl;*/
}
void test_unordered_map3()
{
unordered_map<string, int> countMap;
string strArr[] = { "香蕉","香蕉" ,"水蜜桃","西瓜","苹果","西瓜","香蕉" ,"苹果","西瓜","苹果","苹果","香蕉" ,"水蜜桃" };
for (auto& e : strArr)
{
countMap[e]++;
}
countMap["芒果"] = 10;
for (auto& e : countMap)
{
cout << e.first << ":" << e.second << endl;
}
}
}
typedef详解
搞懂了c++创始人写的< the design and evolution of cpp >中的下面这个例子, 有助于你理解typdef:
typedef int P();
typedef int Q();
class X {
static P(Q); // 等价于static int Q(), Q在此作用域中不再是一个类型
static Q(P); // 等价于static int Q(int ()), 定义了一个名为Q的function
};
隐藏技能:typedef 定义的新类型, 使用时可以省略括号
typedef int NUM;
NUM a = 10; // 也可写成`NUM(a) = 10;`
NUM(b) = 12; // 也可写成`NUM b = 12;`
官方定义
初次接触此类typedef用法的程序员直观上理解这个例子比较困难, 我们来看一下typedef的官方定义:
Typedef does not work like typedef [type] [new name]. The [new name] part does not always come at the end.
You should look at it this way: if [some declaration] declares a variable, typedef [same declaration] would define a type
看我标黑的这句话, 总结一下就是: 任何声明变量的语句前面加上typedef之后,原来是变量的都变成一种类型。不管这个声明中的标识符号出现在中间还是最后
举个例子:
初级:
typedef int x; // 定义了一个名为x的int类型
typedef struct { char c; } s; // 定义名为s的struct类型
typedef int *p; //定义了一个名为p的指针类型, 它指向int (中文描述指针好累)
高级:(注意标识符不一定在最后)
typedef int A[]; // 定义一个名为A的int数组的类型
typedef int f(); // 定义一个名为f, 参数为空, 返回值为int的函数类型
typedef int g(int); // 定义一个名为g, 含一个int参数, 返回值为int的函数类型
这时候我们回头看一开始的那个例子:
typedef int P();
static P(Q);
这样就比较好理解了吧,typedef定义了一个名叫P,参数为空,返回值是int的函数类型,根据我上面介绍的隐藏技能,P(Q)就等价于P Q,声明Q是一个返回值为int
这玩意有什么用呢?
我们都知道C++语言里, 函数都是先声明后使用的(除非在使用之前定义), 看以下例子:
#include <iostream>
#include <stdio.h>
#include <string>
typedef int P(); // 简单的
typedef void Q(int *p, const std::string& s1, const std::string& s2, size_t size, bool is_true); // 复杂的
class X {
public:
P(eat_shit); // 等价于声明`int eat_shit();`
Q(bullshit); // 等价于声明`void bullshit(int *p, const string& s1, const string& s2, size_t size, bool is_true);`
};
int main() {
X *xx;
printf("shit ret: %d\n", xx->eat_shit());
int a[] = {1, 3, 4, 5, 7};
xx->bullshit(a, "foo", "bar", sizeof(a)/sizeof(int), true);
}
int X::eat_shit() {
return 888;
}
void X::bullshit(int *p, const std::string& s1, const std::string& s2, size_t size, bool is_true) {
std::cout << "s1: " << s1 << ", s2: " << s2 << ", size: " << size << std::endl;
printf("elems:\n");
for(int i = 0; i < size; i++) {
printf("%d %s", *p++, (i == size-1) ? "" : ",");
}
printf("\n");
}
总结:
- type (var)(...); // 变量名var与结合,被圆括号括起来,右边是参数列表。表明这是函数指针
- type (var)[]; //变量名var与结合,被圆括号括起来,右边是[]运算符。表示这是数组指针
- type (*var[])...; // 变量名var先与[]结合,说明这是一个数组(至于数组包含的是什么,由旁边的修饰决定)
typename详解
"typename"是一个C++程序设计语言中的关键字。当用于泛型编程时是另一术语"class"的同义词。这个关键字用于指出模板声明(或定义)中的非独立名称(dependent names)是类型名,而非变量名
typename 的作用就是告诉 c++ 编译器,typename 后面的字符串为一个类型名称,而不是成员函数或者成员变量,这个时候如果前面没有 typename,编译器没有任何办法知道 T::LengthType 是一个类型还是一个成员名称(静态数据成员或者静态函数),所以编译不能够通过。
举个例子:假设你现在要针对某一种容器设定一个操作函数
template <class T>
void func (){
T::iteartor * testpt;
}
看到这段代码的时候我们大多数情况下都是可以看出来,这一段代码中的操作是定义了一个容器的迭代器指针类型的变量。但是模版是在编译期间展开的,只有在模版实例化的时候编译器才可以推导出其类型。这段代码对于编译器来说很有可能产生错误的理解,因为我们能快速的根据iteartor是一个迭代器想到这是定义了一个变量,但是对于编译器来说,它怎么会知道一定知道T::iteartor一定是一个迭代器类型,或者一定知道这是一个类型?因为能表示成这样形式的代码有三种情况:
- 在T作用域中存在一个iteartor的静态变量
- 在T作用域中存在一个iteartor的静态成员函数
- 是T类型的成员变量
以上三种含义均可以表示成例子中的样子,编译器怎么知道这是哪一种。在实践过程中,编译器会直接对testpt报错,而且在模板实例化之前,完全没有办法来区分它们,这绝对是滋生各种bug的温床。这时C++标准委员会再也忍不住了,与其到实例化时才能知道到底选择哪种方式来解释以上代码,委员会决定引入一个新的关键字,这就是typename
typename真正的用途
编译期间模版的推导有一个这样的规则:如果解析器在template推导期间遇到了嵌套从属名称,那么不指定他为一个类型,解析器就一定不会把它当成一个类型。
什么是嵌套从属类型?
事实上类型T::const_iterator依赖于模板参数T, 模板中依赖于模板参数的名称称为从属名称(dependent name), 当一个从属名称嵌套在一个类里面时,称为嵌套从属名称(nested dependent name)。 其实T::const_iterator还是一个嵌套从属类型名称(nested dependent type name)。嵌套从属名称是需要用typename声明的,其他的名称是不可以用typename声明的。
总结:嵌套从属名称是需要用typename
声明的,其他的名称是不可以用typename
声明的
T::iteartor
这种,这也就是为什么编译器会对testpt报错的原因。那要怎样指定testpt为一个类型,这就回到了开头的那个问题,我们可以这样解决
template <class T>
void func (){
typename T::iteartor * testpt;
}
加上了typename之后我们就可以知道T::iteartor是一个类型,编译器也可以根据这个进行类型推导了
C++进阶(unordered_set+unordered_map模拟实现)的更多相关文章
- STL set multiset map multimap unordered_set unordered_map example
I decide to write to my blogs in English. When I meet something hard to depict, I'll add some Chines ...
- SilkTest高级进阶系列7-用PostMessage模拟鼠标
SilkTest可以通过调用Windows API来向控件发送消息,从而进行特定的操作.下面这段code使用PostMessage来向计算器上的清除键发送WM_LBUTTONDOWN和WM_LBUTT ...
- [PHP自动化-进阶]002.CURL模拟登录带有验证码的网站
引言:继前文<模拟登录并采集数据>,大家似乎看不过瘾,这会再出一发,模拟实现带验证码网站的登录. 这篇文章主要介绍了PHP使用CURL实现对带有验证码的网站进行模拟登录的方法,可以帮助读者 ...
- [PHP自动化-进阶]001.CURL模拟登录并采集数据
引言:PHP可以通过libcurl实现模拟登录,提交数据,违法乱纪,烧杀抢虐等等事项. 简单说明一下"libcurl",补一下脑: libcurl目前支持http.https.ft ...
- set, unordered_set模板类
template< class Key, class Hash = std::hash<Key>, class KeyEqual = std::equal_to<Key> ...
- c++ unordered_map 自定义key
C++11新增了一类散列容器包括unordered_set, unordered_map, unordered_multiset, unordered_multimap, 即之前熟悉的hash_set ...
- 移动应用抓包调试利器Charles
转载:http://www.jianshu.com/p/68684780c1b0 一.Charles是什么? Charles是在 Mac或Windows下常用的http协议网络包截取工具,是一款屌的不 ...
- python【第八篇】socket网络编程
内容大纲 1.socke基础 两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket. 建 立网络通信连接至少要一对端口号(socket).socket本质是编程接口(API ...
- [翻译] C++ STL容器参考手册 (总册)
1. 写在最前面 这将是博主的第一篇技术博客,思考再三决定从翻译开始.这将是一个系列的博客,由不同的章节组成,章节之间由超链接联系,开发过程将使用增量式开发,每次完成一个章节.本篇是本系列的总册,提供 ...
- C++11 中值得关注的几大变化(网摘)
C++11 中值得关注的几大变化(详解) 原文出处:[陈皓 coolshell] 源文章来自前C++标准委员会的 Danny Kalev 的 The Biggest Changes in C++11 ...
随机推荐
- Codeforces Round #709 Div2
就ac了一题.QAQ掉分了我好难过. A题要画图推出来公式. B不会,现在没看到好懂得题解. C题,思路错了,给一些数,每天只能选其中一些,每个数不能被选超过k次,应该先将每天只能选一个得这个数先选上 ...
- n维偏序 方法记录
题解 首先我们要对一个地点能否到达建立认知:一个地点能到达不仅仅是能从它的上一个点或上上个点跳到,而是能从第一个点开始跳一路跳到.就好比说,咱吃了6个包子吃饱了,但咱不能只付第6个包子的钱. 方法一: ...
- 大数据技术之HBase原理与实战归纳分享-上
@ 目录 概述 定义 特点 数据模型 概述 逻辑结构 物理存储结构 数据模型 应用场景 基础架构 安装 前置条件 部署 启动服务 高可用 Shell操作 基础操作 命令空间 DDL DML 概述 定义 ...
- 强制更改windows电脑密码
强制更改windows电脑密码 1.重启电脑,连续按F8,在出现的高级选项中 2.选择administrator用户,打开管理员命令窗口 3.输入命令 etuserAA123456/add 密码就会被 ...
- 齐博x1标签实例:标签的嵌套用法,调用聚合数据
齐博标签非常强大,可以让不懂程序的你,轻松就能实现所见即所得. 下面跟大家讲解一下,最复杂的运用, 同时使用了union 动态变量参数 与 分页处理标签 比如下面这张图,不仅仅想调用圈子,还想同时调用 ...
- day05-JavaScript02
JavaScript02 8.JavaScript函数 JavaScript函数介绍 函数是由事件驱动的,或者当它被调用时,执行的可重复使用的代码 例子 <!DOCTYPE html> & ...
- Ubuntu定时执行python脚本
Crontab命令 命令选项: crontab -l : 显示某个用户的任务计划 crontab -e :编辑某个用户的任务计划 cron服务 service cron start/stop/rest ...
- Day03.1:初学者安装IDEA后需要知道的小技巧
初学者安装IDEA后需要知道的小技巧 1.输入psvm直接生成 main方法 2.输入sout可以直接生成输出语句 3.代码放大设置 4.注释颜色更改 5.代码字体大小通过Ctrl+鼠标滑轮控制的设置 ...
- Perl引用
引用就是C语言中的指针,perl引用是一个标量类型可以指向变量.数组.哈希表(也叫关联数组)甚至子程序,可以应用在程序的任何地方. 在变量前面加一个\就得到了这个变量的一个引用 #!usr/bin/p ...
- XMind2022安装激活
1. 下载XMind2021并安装 2. 激活安装 2.1 找到需要替换文件的目录右击软件,打开文件所在的位置(默认路径:C:\Program Files\XMind),找到resources文件夹 ...