关联容器
关联容器和顺序容器有着根本的不同:关联容器中的元素是按关键字来保持和访问的。与之相对,顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。和顺序容器一样的是,关联容器也是模板
关联容器类型:
按关键字有序保存元素
map 关联数组:保存关键字-值对
set 关键字即值,即只保存关键字的容器
multimap 关键字可以重复出现的 map
mutiset 关键字可以重复出现的 set
无序集合
unordered_map 用哈希函数组织的 map
unordered_set 用哈希函数组织的 set
unordered_multimap 哈希组织的 map:关键字可以重复出现
unordered_multimap 哈希组织的 set:关键字可以重复出现
使用 map:
1 #include <iostream> 2 #include <map> 3 using namespace std; 4 5 int main(void){ 6 map<string, size_t> word_count;//string到size_t的空map 7 string word; 8 while(cin >> word){ 9 ++word_count[word];//若word在map中则对应值加一,若不在则创建一个关键字为word值为0的元素并将值加一 10 } 11 auto pos = word_count.begin();//pos为word_count的首迭代器 12 // pos->first += "j";//map容器(关联容器)的key值是const的 13 pos->second += 1; 14 15 for(const auto &w : word_count){ 16 cout << w.first << " " << w.second << endl; 17 } 18 19 return 0; 20 }
注意:若 word 在 map 中则对应值加一,若不在则创建一个关键字为 word 值为 0 的元素并将值加一
map 容器(关联容器)的 key 是 const 的
使用 set:
1 #include <iostream> 2 #include <map> 3 #include <set> 4 using namespace std; 5 6 int main(void){ 7 map<string, size_t> word_count;//string到size_t的空map 8 set<string> exclude = {"the", "but", "and", "or", "an", "a"}; 9 auto cnt = exclude.begin();//cnt为exclude的首元素 10 // *cnt = "The";//错误,set容器(关联容器)的key是const的 11 12 string word; 13 14 while(cin >> word){ 15 if(exclude.find(word) == exclude.end())//word不在exclude中 16 ++word_count[word];//若word在map中则对应值加一,若不在则创建一个关键字为word值为0的元素并将值加一 17 } 18 auto pos = word_count.begin();//pos为word_count的首迭代器 19 // pos->first += "j";//错误,map容器(关联容器)的key是const的 20 pos->second += 1; 21 22 for(const auto &w : word_count){ 23 cout << w.first << " " << w.second << endl; 24 } 25 26 return 0; 27 }
注意:set 容器(关联容器)的 key 是 const 的
关联容器概述:
关联容器不支持顺序容器的位置相关的操作,如:psuh_front 或 push_back。原因是关联容器中的元素是根据关键字存储的,这些操作对关联容器没有意义。而且,关联容器也不支持构造函数或插入操作这些接受一个元素值和一个数量值的操作。
除了与顺序容器相同的操作之外,关联容器还支持一些顺序容器不支持的操作和类型别名。此外,无序容器还提供一些用来调整哈希性能的操作。
关联容器的迭代器都是双向的
map/set 初始化:
1 #include <iostream> 2 #include <set> 3 #include <map> 4 using namespace std; 5 6 int main(void){ 7 map<string, size_t> a = { 8 {"jfk", 1}, 9 {"jfskl", 1}, 10 {"wei", 2}, 11 {"jflsdfd", 11}, 12 {"fjks", 12}, 13 };//使用列表初始化map 14 map<string, size_t> b, c(a.cbegin(), a.cend());//和顺序容器一样,可以使用一对顺序容器构造初始化 15 // map<char *, size_t> d(a.cbegin(), a.cend());//错误,类型不完全相同不能使用范围迭代器构造初始化 16 map<char *, size_t> d; 17 // d = a;//错误,只有类型完全相同才能直接赋值,和顺序容器一样 18 b = a;//类型相同的map可以直接拷贝赋值 19 20 set<string> i, e = {"fjkls", "f", "fd", "fds", "fjlsjf"};//使用列表初始化set 21 set<string> f, g(e.cbegin(), e.cend());//和顺序容器一样,可以使用一对顺序容器构造初始化 22 // set<char *> h(e.cbegin(), e.cend());////错误,类型不完全相同不能使用范围迭代器构造初始化 23 set<char *> h; 24 // h = e;//错误,只有类型完全相同才能直接拷贝赋值,和顺序容器一样 25 i = e;//类型相同的set可以直接拷贝赋值 26 27 return 0; 28 }
注意:关键字可重复的 map/set,无序 map/set 等这 8 种关联容器的初始化方式完全相同
支持使用列表初始化
类型必须完全相同的容器对象之间才可以拷贝赋值
类型必须完全相同的容器对象之间才可以使用一对范围迭代器构造初始化
关键字类型的要求:
关联 容器对其关键字类型有一些限制。对于有序容器——map、multimap、set 以及 multiset,关键字类型必须定义元素比较的方法。默认情况下,标准库使用关键字类型的 < 运算符来比较两个关键字。在集合类型中,关键字类型就是元素的类型;在映射类型中,关键字类型是元素的第一部分类型。
传递给排序算法的可调用对象必须满足与关联容器中关键字一样的类型要求。
有序容器的关键字类型:
可以向一个算法提供我们自己定义的比较操作,与之类似,也可以提供我们自己定义的操作来代替关键字上的 < 运算符。所提供的操作必须在关键字类型上定义一个严格弱序。可以将严格弱序看作 "小于等于",虽然实际上定义的操作可能是一个复杂的函数。在实际编程中,重要的是,如果一个类型定义了 "行为正常" 的 < 运算符,则它可以作为关键字类型。
使用关键字类型的比较函数:
用来组织一个容器中元素的操作的类型也是该容器的一部分。为了指定使用自定义的操作,必须在定义关联容器类型时提供此操作的类型。用尖括号指出要定义哪种类型的容器,自定义的操作必须在尖括号中紧跟着类型给出:
1 #include <iostream> 2 #include <map> 3 #include <set> 4 using namespace std; 5 6 class gel{ 7 friend bool compare(const gel&, const gel&); 8 friend ostream& operator<<(ostream&, const gel&); 9 10 private: 11 int x, y, z; 12 13 public: 14 gel(int a, int b, int c) : x(a), y(b), z(c) {} 15 gel(int a, int b) : gel(a, b, 0) {} 16 gel(int a) : gel(a, 0, 0) {} 17 gel() : gel(0, 0, 0) {} 18 ~gel() {} 19 }; 20 21 bool compare(const gel &it1, const gel &it2){ 22 return it1.x <= it2.x; 23 } 24 25 ostream& operator<<(ostream &os, const gel &it){ 26 cout << it.x << " " << it.y << " " << it.z; 27 } 28 29 int main(void){ 30 set<gel, decltype(compare)*> a(compare);//此处定义a时我们必须提供两个类型: 31 //关键字类型以及比较操作类型(一种函数指针类型),并且用compare函数指针初始化a对象 32 //这表示当我们向a中添加元素时按照compare比较的结果排序 33 a = {gel(1, 2, 3), gel(2, 3, 4), gel(1, 5, 6)}; 34 for(const auto &indx : a){ 35 cout << indx << endl; 36 } 37 cout << endl; 38 map<gel, int, decltype(compare)*> b(compare);//注意,当用decltype来获得一个函数指针类型时,必须加上一个*来指出我们要使用一个给定函数类型的指针 39 //此处定义map类型对象我们要提供三个类型:关键字类型,值类型,比较类型(一种函数指针类型) 40 b = {{gel(1, 2, 3), 1}, {gel(2, 4, 5), 11}}; 41 for(const auto &indx : b){ 42 cout << indx.first << " " << indx.second << endl; 43 } 44 return 0; 45 }
注意:我们必须使用对应的函数指针来初始化对象的比较函数指针参数,不然插入元素时会因为没有指定比较函数而error
在尖括号中出现的每个类型,就仅仅是一个类型而已。当我们创建一个容器(对象)时,才会以后再函数参数的形式提供真正的比较操作。
为了使用自己定义的操作,在定义 set 对象时我们必须提供两个类型:关键字类型 gel 以及比较操作类型——应该是一种函数指针类型,在本例中我们提供了一个指向 compare 函数的指针。对于定义 map 等其它对象也是一样~
当用 decltype 来获得一个函数指针类型时,必须加上一个 * 来指出我们要使用一个给定函数类型的指针
当然,我们也可以直接重载 < 运算符咯:
1 #include <iostream> 2 #include <set> 3 #include <map> 4 using namespace std; 5 6 class gel{ 7 friend bool operator<(const gel&, const gel&); 8 9 private: 10 int x, y, z; 11 12 public: 13 gel(int a, int b, int c) : x(a), y(b), z(c) {} 14 gel(int a, int b) : gel(a, b, 0) {} 15 gel(int a) : gel(a, 0, 0) {} 16 gel() : gel(0, 0, 0) {} 17 ~gel() {} 18 }; 19 20 bool operator<(const gel &it1, const gel &it2){ 21 return it1.x <= it2.x; 22 } 23 24 int main(void){ 25 set<gel> a = {gel(1, 2, 3), gel(2, 3, 4), gel(3, 4, 6)}; 26 map<gel, int> b = { 27 {gel(1, 2, 3), 1}, 28 {gel(2, 3, 4), 2}, 29 {gel(3, 4, 5), 10}, 30 {gel(1, 2, 3), 44} 31 }; 32 return 0; 33 }
好像这东东有点类似于 sort 这样的泛型算法,使用关键字类型比较函数相当于谓词,sort 也可以直接重载 < 运算符
关联容器操作
关联容器额外的类型别名
key_type 此关联容器的关键字类型
mapped_type 每个关键字关联的类型:只使用于 map
value_type 对于 set,与 key_type 相同
对于 map 为 pair<const key_type,mapped_type>
1 #include <iostream> 2 #include <map> 3 #include <set> 4 using namespace std; 5 6 ostream& operator<<(ostream &os, const pair<string, int> &it){ 7 os << it.first << " " << it.second; 8 return os; 9 } 10 11 int main(void){ 12 set<string>::value_type v1("i'm a string");//v1是一个string 13 set<string>::key_type v2("i'm a string");//v2是一个string 14 15 map<string, int>::value_type v3("i'm a pair<const string, int>", 1);//v3是一个pair<const string, int> 16 map<string, int>::key_type v4("i'm a string");//v4是一个string 17 map<string, int>::mapped_type v5(1);//v5是一个int 18 19 cout << v1 << endl; 20 cout << v2 << endl; 21 cout << v3 << endl; 22 cout << v4 << endl; 23 cout << v5 << endl; 24 25 return 0; 26 }
注意:只有 map 类型(unordered_map,unordered_multimap,multimap 和 map)才定义了 mapped_type
关联容器迭代器:
当解引用一个关联容器迭代器时,我们会得到一个类型为容器的 value_type 的值的引用。对 map 而言,value_type 时一个 pair 类型,其 first 成员保存 const 的关键字,second 成员保存值:
1 #include <iostream> 2 #include <map> 3 using namespace std; 4 5 int main(void){ 6 map<string, size_t> mp{{"jf", 1}, {"jfl", 2}, {"jflsf", 3}}; 7 auto map_it = mp.begin();//获得指向mp中一个元素的迭代器 8 //*map_it是指向一个pair<const string, size_t>对象的引用 9 cout << map_it->first << endl;//打印此元素的关键字 10 cout << map_it->second << endl;//打印此元素的值 11 // map_it->first = "jfsl";//错误,关键字是const的 12 ++map_it->second;//正确,可以通过迭代器改变元素 13 14 return 0; 15 }
注意:一个 map 的 value_type 是一个 pair,我们可以改变 pair 的值,但不能改变关键字成员的值。
set 的迭代器是 const 的
遍历关联容器:
1 #include <iostream> 2 #include <map> 3 #include <set> 4 using namespace std; 5 6 int main(void){ 7 map<int, int> mp = {{1, 2}, {10, 3}, {-1, 4}}; 8 for(const auto &indx : mp){ 9 cout << indx.first << " " << indx.second << endl; 10 }//map是按关键字字典序升序排列的 11 12 set<int> st = {1, -1, 300, -30}; 13 for(const auto &indx : st){ 14 cout << indx << " "; 15 } 16 cout << endl;//set也是按关键字字典序升序排列的 17 18 return 0; 19 }
注意:当一个迭代器遍历一个 map、multimap、set 或 multiset 时,迭代器按关键字升序遍历元素
关联容器和算法:
由于关键字是 const 的特性意味着我们不能将关联容器传递给修改或重排元素的算法,我们通常不对关联容器使用泛型算法。而且关联容器使用容器自定义的算法通常会比使用泛型算法更快。
在实际编程中,如果我们真要对一个关联容器使用算法,要么是将它当作一个源序列,要么当作一个目的的位置。例如,可以使用泛型 copy 算法将元素匆匆一个关联容器拷贝到另一个序列。类似的,可以调用 inserter 将一个插入器绑定到一个关联容器。通过使用 inserter,我们可以将关联容器当作一个目的位置来调用另一个算法:
1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 #include <set> 5 using namespace std; 6 7 int main(void){ 8 multiset<string> c = {"fjskl", "a", "b", "fsdf"}; 9 vector<string> v = {"qw", "we", "ertr"}; 10 11 copy(v.begin(), v.end(), inserter(c, c.end()));//正确,set中有push成员 12 // copy(v.begin(), v.end(), back_inserter(c));//错误,set中没用push_back成员 13 14 copy(c.begin(), c.end(), inserter(v, v.end()));//正确 15 copy(c.begin(), c.end(), back_inserter(v));//正确 16 17 return 0; 18 }
添加元素:
1 #include <iostream> 2 #include <vector> 3 #include <map> 4 #include <set> 5 using namespace std; 6 7 int main(void){ 8 vector<int> ivec = {2, 4, 6, 8, 2, 4, 6, 8};//ivec有8个元素 9 set<int> st1;//空集合 10 st1.insert(ivec.cbegin(), ivec.cend());//st1中有4个元素 11 st1.insert({1, 3, 5, 7, 1, 3, 5, 7});//现在st1中有8个元素 12 13 map<int, int> mp2, mp1 = {{1, 2}, {1, 3}, {2, 4}};//关键字已存在的元素会被忽略 14 cout << mp1.size() << endl;//2 15 mp2.insert({1, 2}); 16 mp2.insert(make_pair(2, 3)); 17 mp2.insert(pair<int, int>(4, 5)); 18 mp2.insert(map<int, int>::value_type(1, 1)); 19 mp2.insert(mp1.cbegin(), mp1.cend()); 20 21 return 0; 22 }
注意:insert 有两个版本,分别接受一对迭代器,或是一个初始化器列表
对于非 multi 的关联容器,对于一个给定的关键字,只有第一个带此关键字的元素才被插入到容器中
对一个 map 进行 insert 操作时,元素类型必须是 pair。通常若是没用一个现成的 pair 对象,可以在 insert 参数列表中创建一个 pair
关联容器 insert 操作:
c.insert(v) v 是 value_type 类型的对象,args 用来构造一个元素
c.emplace(args) 对于 map 和 set,只有当元素不在 c 中时才插入/构造元素。函数返回一个 pair,
包含一个迭代器,指向具有指定关键字的元素,以及一个指示插入是否成功的
bool 值。对于 multiset 和 multimap,总会插入/构造给定元素,并返回一个指向
新元素的迭代器
c.insert(b, e) b 和 e 是一对迭代器,表示一个 c::value_type 类型值的范围;il 是
c.insert(il) 这种值的花括号列表。函数返回 void
对于 map 和 set,只有插入关键字不在 c 中的元素。对于 multiset 和 multimap,
则会插入范围中的每个元素
c.insert(p, v) 类似 insert(v) 或 empalce(args),但将迭代器 p 作为一个指示,指出从哪里开始
c.emlace(p, args) 搜索新元素应该存储的位置。返回一个迭代器,指向具有给定关键字的元素
删除元素:
从关联容器删除元素:
c.erase(k) 从 c 中删除每个关键字为 k 的元素。返回一个 size_type 值,指出删除的元素的数量
c.erase(p) 从 c 中删除迭代器 p 指定的元素。p 必须指向 c 中一个真实元素,不能等于 c.end()。
返回一个指向 p 之后元素的迭代器
c.erase(b, e) 删除迭代器对 b 和 e 所表示范围(左闭右开)中的元素。返回 e
下标操作:
map 和 unordered _map 的下标操作:
c[k] 返回关键字为 k 的元素:如果 k 不在 c 中,添加一个关键字为 k 的元素并对其进行初始化
c.at(k) 访问关键字为 k 的元素,带参数检查:若 k 不在 c 中,抛出一个 out_of_range 异常
set 类型不支持下标操作,因为 set 中没有与关键字像关联的 "值"。元素本身就是关键字,因此 "获取一个与关键字像关联的值" 的操作是没有意义的
map 的下标操作与一般下标操作的不同:
如果关键字不在 map 中,会以该关键字创建一个元素插入到 map 中,其关联值将进行值初始化
通常情况下,解引用一个迭代器所返回的类型与下标运算返回的类型是一样的。但对 map 则不然:当对一个 map 进行下标操作时,会获得一个 mapped_type 对象,但当解引用一个 map 迭代器时,会得到一个 value_type 对象
1 #include <iostream> 2 #include <stdexcept> 3 #include <map> 4 using namespace std; 5 6 int main(void){ 7 map<int, int> m; 8 try{ 9 cout << m.at(0) << endl;//关键字0不在m中,抛出out_of_range异常 10 }catch(out_of_range err){ 11 cout << err.what() << endl; 12 } 13 14 m[0];//0不在m中,将0插入m中并将其关联值初始化为0 15 16 try{ 17 cout << m.at(0) << endl;//此时0已经在m中且其关联值为0 18 }catch(out_of_range err){ 19 cout << err.what() << endl; 20 } 21 22 auto a = m[0];//下标操作得到mapped_type类型即int 23 cout << a << endl;//0 24 25 auto b = *m.begin();//解引用操作得到value_type类型即pair<int, int> 26 cout << b.first << " " << b.second << endl;//0 0 27 28 return 0; 29 }
访问元素:
在一个关联容器中查找元素的操作:
lower_bound 和 upper_bound 不适用于无序容器
下标和 at 只适用于非 const 的 map 和 unordered_map
c.find(k) 返回一个迭代器,指向第一个关键字为 k 的元素,若 k 不在容器中则返回尾后迭代器
c.count(k) 返回关键字等于 k 的元素的数量。对于不允许元素重复的容器返回值永远是 0 或 1
c.lower_bound(k) 返回一个迭代器,指向第一个关键字不小于 k 的元素
c.upper_bound(k) 返回一个迭代器,指向第一个关键字大于 k 的元素
c.equal_range(k) 返回一个迭代器 pair,表示关键字等于 k 的元素的范围。若 k 不存在,pair 的两个成员
均等于 c.end()
对 map 使用 find 代替下标操作:
由于下标操作如果查找的关键字不在容器中会被插入,这不一定符合我们的预期。如果仅仅是在 map 容器中查找元素,我们应该使用 find 操作
在 multimap 或 multiset 中查找元素:
1 #include <iostream> 2 #include <map> 3 #include <set> 4 using namespace std; 5 6 int main(void){ 7 int f = 2; 8 multimap<int, int> mp = {{1, 1}, {3, 2}, {3, 2}, {2, 1}, {1, 1}, {2, 2}}; 9 for(const auto &indx : mp){ 10 cout << indx.first << " " << indx.second << endl;//按照关键字升序排列,关键字相同的按照关联值升序排列,因此关键字相同的元素会相邻存储 11 } 12 cout << endl; 13 auto counts1 = mp.count(f); 14 auto it1 = mp.find(f); 15 while(counts1){ 16 cout << it1->first << " " << it1->second << endl; 17 ++it1; 18 --counts1; 19 } 20 cout << endl; 21 22 multiset<int> st = {1, 2, 3, 1, 2, 3, 2, 1, 2, 2};//关键字自动按升序排列 23 auto counts2 = st.count(f); 24 auto it2 = st.find(f); 25 while(counts2){ 26 cout << *it2 << " "; 27 --counts2; 28 } 29 cout << endl; 30 31 return 0; 32 }
注意:和 map 以及 set 一样,multimap 和 multiset 都是按照关键字升序排列的,对于 multimap 中关键字相同的元素则按照关联值升序排列。因此关键字相同的元素会相邻存储
用 lower_bound / upper_bound 重写上面的代码:
1 #include <iostream> 2 #include <algorithm> 3 #include <map> 4 #include <set> 5 using namespace std; 6 7 int main(void){ 8 int f = 2; 9 multimap<int, int> mp = {{1, 1}, {3, 2}, {3, 2}, {2, 1}, {1, 1}, {2, 2}}; 10 for(const auto &indx : mp){ 11 cout << indx.first << " " << indx.second << endl;//按照关键字升序排列,关键字相同的按照关联值升序排列,因此关键字相同的元素会相邻存储 12 } 13 cout << endl; 14 auto it1_l = mp.lower_bound(f); 15 auto it1_r = mp.upper_bound(f); 16 while(it1_l != it1_r){ 17 cout << it1_l->first << " " << it1_l->second << endl; 18 ++it1_l; 19 } 20 cout << endl; 21 22 multiset<int> st = {1, 2, 3, 1, 2, 3, 2, 1, 2, 2};//关键字自动按升序排列 23 auto it2_l = st.lower_bound(f); 24 auto it2_r = st.upper_bound(f); 25 while(it2_l != it2_r){ 26 cout << *it2_l << " "; 27 ++it2_l; 28 } 29 cout << endl; 30 31 return 0; 32 }
注意:lower_bound 和 upper_bound 都是二分实现的,效率比上面的代码要高,但是同时也要求给出的序列一定是按关键字升序排列的
如果关键字不在容器中,则 lower_bound 和 upper_bound 会返回相等的迭代器——指向一个不影响排序的关键字插入位置(我用g++11编译如果关键字不存在则两者都返回end迭代器)
用 equal_range 函数重写上面的代码:
1 #include <iostream> 2 #include <algorithm> 3 #include <map> 4 #include <set> 5 using namespace std; 6 7 int main(void){ 8 int f = 2; 9 multimap<int, int> mp = {{1, 1}, {3, 2}, {3, 2}, {2, 1}, {1, 1}, {2, 2}}; 10 for(const auto &indx : mp){ 11 cout << indx.first << " " << indx.second << endl;//按照关键字升序排列,关键字相同的按照关联值升序排列,因此关键字相同的元素会相邻存储 12 } 13 cout << endl; 14 auto it1 = mp.equal_range(f); 15 while(it1.first != it1.second){ 16 cout << it1.first->first << " " << it1.first->second << endl; 17 ++it1.first; 18 } 19 cout << endl; 20 21 multiset<int> st = {1, 2, 3, 1, 2, 3, 2, 1, 2, 2};//关键字自动按升序排列 22 auto it2 = st.equal_range(f); 23 while(it2.first != it2.second){ 24 cout << *it2.first << " "; 25 ++it2.first; 26 } 27 cout << endl; 28 29 return 0; 30 }
无序容器:
c++11标准定义了 4 个无序关联容器。这些容器不是使用比较运算来组织元素,而是使用一个哈希函数和关键字类型的 == 运算符。如果关键字类型固有就是无序的,或者性能测试发现问题可以用哈希技术解决,就可以使用无序容器。
使用无序容器:
除了哈希管理操作之外,无序容器还提供了与有序容器相同的操作(find, insert 等)。这意味着我们之前用于 map 和 set 的操作也能用于 unordered_map 和 unordered_set。类似的,无序容器也有 multi 版本。
使用 unordered_map:
1 #include <iostream> 2 #include <unordered_map> 3 using namespace std; 4 5 int main(void){ 6 unordered_map<string, size_t> word_count;//string到size_t的空map 7 string word; 8 while(cin >> word){ 9 ++word_count[word];//若word在map中则对应值加一,若不在则创建一个关键字为word值为0的元素并将值加一 10 } 11 auto pos = word_count.begin();//pos为word_count的首迭代器 12 // pos->first += "j";//map容器(关联容器)的key值是const的 13 pos->second += 1; 14 15 for(const auto &w : word_count){ 16 cout << w.first << " " << w.second << endl; 17 } 18 19 return 0; 20 }
使用 unordered_set:
1 #include <iostream> 2 #include <unordered_map> 3 #include <unordered_set> 4 using namespace std; 5 6 int main(void){ 7 unordered_map<string, size_t> word_count;//string到size_t的空map 8 unordered_set<string> exclude = {"the", "but", "and", "or", "an", "a"}; 9 auto cnt = exclude.begin();//cnt为exclude的首元素 10 // *cnt = "The";//错误,set容器(关联容器)的key是const的 11 12 string word; 13 14 while(cin >> word){ 15 if(exclude.find(word) == exclude.end())//word不在exclude中 16 ++word_count[word];//若word在map中则对应值加一,若不在则创建一个关键字为word值为0的元素并将值加一 17 } 18 auto pos = word_count.begin();//pos为word_count的首迭代器 19 // pos->first += "j";//错误,map容器(关联容器)的key是const的 20 pos->second += 1; 21 22 for(const auto &w : word_count){ 23 cout << w.first << " " << w.second << endl; 24 } 25 26 return 0; 27 }
注意:无序容器中关键字相同的元素也是相邻存储的
可以发现,除了无序容器提供了特有的管理桶函数外有序和无序版本的关联容器用法基本一致
管理桶:
无序容器在存储上组织为一组桶,每个桶保存零个或多个元素。无序容器使用一个哈希函数将元素映射到桶。为了访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个桶。容器将具有一个特定哈希值的所有元素保存在相同的桶中。如果容器允许重复关键字,所有具有相同关键字的元素也都会在同一个桶中。实际上这就是链地址法哈希。
无序容器提供了一组管理桶的函数,这些成员函数允许我们查询容器的状态以及在必要时强制容器重组
无序容器管理操作:
桶接口:
c.bucket_count() 正在使用的桶的数目
c.max_bucket_count() 容器能容纳的最多桶的数目
c.bucket_size(n) 第 n 个桶中有多少元素
c.bucket(k) 关键字为 k 的元素在哪个桶中
桶迭代
local_iterator 可以用来访问桶中元素的迭代器类型
const_local_iterator 桶迭代器的 const 版本
c.begin(n), c.end(n) 桶 n 的首元素迭代器和尾后迭代器
c.cbegin(n), c.cend(n) 与前两个函数类似,但返回 const_local_iterator
哈希策略
c.load_factor() 每个桶的平均元素数量,返回 float 值
c.max_load_factor() c 试图维护的平均桶大小,返回 float 值。c 会在需要时添加新的桶,以使得
load_factor <= max_load_factor
c.rehash(n) 重组存储,使得 bucket_count >= n
且 bucket_count > size / max_load_factor
c.reserve(n) 重组存储,使得 c 可以保存 n 个元素且不必 rehash
无序容器对关键字类型的要求:
默认情况下,无序容器使用关键字类型的 == 来比较元素,它们还使用一个 hash<key_type> 类型的对象来生成每个元素的哈希值。标准库为内置类型(包括指针)提供了 hash 模板。还为一些标准库类型,包括 string 和智能指针定义了 hash。因此,我们可以直接定义关键字是内置类型(包括指针类型)、string 还是智能指针类型的无序容器。
但是,我们不能直接定义关键字类型为自定义类型的无序容器。和前面用自定义类型做有序容器关键字类似,我们可以通过自己提供 hash 函数和类型比较函数来使用自定义类型做无序容器关键字:
1 #include <iostream> 2 #include <unordered_map> 3 #include <unordered_set> 4 using namespace std; 5 6 class gel{ 7 friend ostream& operator<<(ostream&, const gel&); 8 9 private: 10 int x, y, z; 11 12 public: 13 gel(int a, int b, int c) : x(a), y(b), z(c) {} 14 gel(int a, int b) : gel(a, b, 0) {} 15 gel(int a) : gel(a, 0, 0) {} 16 gel() : gel(0, 0, 0) {} 17 ~gel() {} 18 19 int get_id(void) const{ 20 return x; 21 } 22 }; 23 24 ostream& operator<<(ostream &os, const gel &it){ 25 cout << it.x << " " << it.y << " " << it.z; 26 } 27 28 bool eq(const gel &it1, const gel &it2){ 29 return it1.get_id() == it2.get_id(); 30 } 31 32 size_t hasher(const gel &it){ 33 return hash<int>()(it.get_id());//使用标准库hash类型来计算x成员的哈希值 34 } 35 36 int main(void){ 37 unordered_set<gel, decltype(hasher)*, decltype(eq)*> a(42, hasher, eq);//三个参数分别是桶大小,哈希函数指针,相等性判断函数指针 38 a = {gel(1, 2, 3), gel(2, 3, 4), gel(1, 5, 6)}; 39 for(const auto &indx : a){ 40 cout << indx << endl; 41 } 42 cout << endl; 43 unordered_map<gel, int, decltype(hasher)*, decltype(eq)*> b(42, hasher, eq); 44 b = {{gel(1, 2, 3), 1}, {gel(2, 4, 5), 11}}; 45 for(const auto &indx : b){ 46 cout << indx.first << " " << indx.second << endl; 47 } 48 49 return 0; 50 }
我们也可以用自定义类型 == 运算符重载来代替上面代码中的类型比较函数:
1 #include <iostream> 2 #include <unordered_map> 3 #include <unordered_set> 4 using namespace std; 5 6 class gel{ 7 friend ostream& operator<<(ostream&, const gel&); 8 friend bool operator==(const gel&, const gel&); 9 10 private: 11 int x, y, z; 12 13 public: 14 gel(int a, int b, int c) : x(a), y(b), z(c) {} 15 gel(int a, int b) : gel(a, b, 0) {} 16 gel(int a) : gel(a, 0, 0) {} 17 gel() : gel(0, 0, 0) {} 18 ~gel() {} 19 20 int get_id(void) const{ 21 return x; 22 } 23 }; 24 25 ostream& operator<<(ostream &os, const gel &it){ 26 cout << it.x << " " << it.y << " " << it.z; 27 } 28 29 bool operator==(const gel &it1, const gel &it2){ 30 return it1.get_id() == it2.get_id(); 31 } 32 33 // bool eq(const gel &it1, const gel &it2){ 34 // return it1.get_id() == it2.get_id(); 35 // } 36 37 size_t hasher(const gel &it){ 38 return hash<int>()(it.get_id());//使用标准库hash类型来计算x成员的哈希值 39 } 40 41 int main(void){ 42 unordered_set<gel, decltype(hasher)*> a(42, hasher);//重载了gel类的==运算符后我们可以不使用相等性判断函数 43 a = {gel(1, 2, 3), gel(2, 3, 4), gel(1, 5, 6)}; 44 for(const auto &indx : a){ 45 cout << indx << endl; 46 } 47 cout << endl; 48 unordered_map<gel, int, decltype(hasher)*> b(42, hasher); 49 b = {{gel(1, 2, 3), 1}, {gel(2, 4, 5), 11}}; 50 for(const auto &indx : b){ 51 cout << indx.first << " " << indx.second << endl; 52 } 53 54 return 0; 55 }
注意:当用 decltype 来获得一个函数指针类型时,必须加上一个 * 来指出我们要使用一个给定函数类型的指针