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模拟实现)的更多相关文章

  1. 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 ...

  2. SilkTest高级进阶系列7-用PostMessage模拟鼠标

    SilkTest可以通过调用Windows API来向控件发送消息,从而进行特定的操作.下面这段code使用PostMessage来向计算器上的清除键发送WM_LBUTTONDOWN和WM_LBUTT ...

  3. [PHP自动化-进阶]002.CURL模拟登录带有验证码的网站

    引言:继前文<模拟登录并采集数据>,大家似乎看不过瘾,这会再出一发,模拟实现带验证码网站的登录. 这篇文章主要介绍了PHP使用CURL实现对带有验证码的网站进行模拟登录的方法,可以帮助读者 ...

  4. [PHP自动化-进阶]001.CURL模拟登录并采集数据

    引言:PHP可以通过libcurl实现模拟登录,提交数据,违法乱纪,烧杀抢虐等等事项. 简单说明一下"libcurl",补一下脑: libcurl目前支持http.https.ft ...

  5. set, unordered_set模板类

    template< class Key, class Hash = std::hash<Key>, class KeyEqual = std::equal_to<Key> ...

  6. c++ unordered_map 自定义key

    C++11新增了一类散列容器包括unordered_set, unordered_map, unordered_multiset, unordered_multimap, 即之前熟悉的hash_set ...

  7. 移动应用抓包调试利器Charles

    转载:http://www.jianshu.com/p/68684780c1b0 一.Charles是什么? Charles是在 Mac或Windows下常用的http协议网络包截取工具,是一款屌的不 ...

  8. python【第八篇】socket网络编程

    内容大纲 1.socke基础 两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket. 建 立网络通信连接至少要一对端口号(socket).socket本质是编程接口(API ...

  9. [翻译] C++ STL容器参考手册 (总册)

    1. 写在最前面 这将是博主的第一篇技术博客,思考再三决定从翻译开始.这将是一个系列的博客,由不同的章节组成,章节之间由超链接联系,开发过程将使用增量式开发,每次完成一个章节.本篇是本系列的总册,提供 ...

  10. C++11 中值得关注的几大变化(网摘)

    C++11 中值得关注的几大变化(详解) 原文出处:[陈皓 coolshell] 源文章来自前C++标准委员会的 Danny Kalev 的 The Biggest Changes in C++11 ...

随机推荐

  1. 关于AWS-EC2或者多个资源的tag的批量添加-基于Resource Groups & Tag Editor 和 命令处理

    今天收到一个请求,需要对公司所有的ec2-添加上两个成本IO标签,因为机器太多了 想到了如下两种方案去批量处理 方案一:利用aws的 [Management Tools]下的 Resource Gro ...

  2. 【博学谷学习记录】超强总结,用心分享|Linux修改文件权限方法总结

    一.介绍 linux中"一切皆文件".每个文件都设定了针对不同用户的访问权限. 文件权限主要针对以下三种对象: 属主:拥有者 属组:所属的组 其他人:不属于上述两类 二.文件权限 ...

  3. Sqoop 之 安装

    Sqoop 之 安装 前言 安装 Sqoop 的前提是已经具备 Java 和 Hadoop 的环境. 一.下载并解压 1) 下载地址:http://mirrors.hust.edu.cn/apache ...

  4. HFS局域网分享文件的神器(附下载链接)

    温馨提示,下载链接在页末 前言 假如说你需要传递个学习资料给好基友,我们有许多种方式可选:硬盘媒介.网络分享等. 要是论速度,还是得拿3.0或以上的U盘来拷贝,确实神速哈哈.但是其也有局限性,比如需要 ...

  5. 强国杯东杯分区赛miscwp

    ​ 目录 不要被迷惑 PCAP文件分析 平正开 不要被迷惑 ​编辑 导出http ​编辑 得到flag.zip后直接爆破密码 ​编辑 得到​编辑 然后一键解码 ​编辑 flag{WImuJeqSNPh ...

  6. 10.pygame-碰撞检测

    添加并监听英雄发射子弹事件 class Hero(GameSprite): def __init__(self): # 调用父类方法,设置image super().__init__('./image ...

  7. pod(五):pod hook(pod钩子)和优雅的关闭nginx pod

    目录 一.系统环境 二.前言 三.pod hook(pod钩子) 四.如何优雅的关闭nginx pod 一.系统环境 服务器版本 docker软件版本 Kubernetes(k8s)集群版本 CPU架 ...

  8. vue-axios 输入参数获取数据的写法

    <template> <div class="nav"> <input v-model="name" type="tex ...

  9. ES 客户端 RestHighLevelClient Connection reset by peer 亲测有效 2022-11-05

    导读 最新公司ES集群老出现连接关闭,进而导致查询|写入ES时报错,报错日志显示如下 [2m2022-10-23 14:13:10.088[0;39m - [31mERROR[0;39m - [35m ...

  10. pinpoint:初始化hbase数据库

    安装完成hbase之后,需要初始化hbase的pinpoint库(创建表). 1. 登录数据库 [root@monitor default]# cd /home/pinpoint/hbase-1.7. ...