《C++ Primer》读书笔记—第十一章 关联容器

声明:

  • 文中内容收集整理自《C++ Primer 中文版 (第5版)》,版权归原书所有。
  • 学习一门程序设计语言最好的方法就是练习编程

一、使用关联容器

1、关联容器中的元素按关键字保存和访问,顺序容器中的元素则按照它们在容器中的位置来顺序保存访问。关联容器支持高效的关键字查找和访问。

2、两个主要的关联容器是map和set

  map中的元素是一些关键字值对:关键字起索引作用,值表示与索引相关联的数据。

  set中每个元素包含一个关键字;set支持高效的关键字查询操作--检查一个指定的关键字是否在set中。

3、两个主要的关联容器是map和set,细分为8种

  • 有序
    • 关键字不可重复
      • map
      • set
    • 关键字可重复
      • multimap
      • multiset
  • 无序
    • 关键字不可重复
      • unordered_map
      • unordered_set
    • 关键字可重复
      • unordered_multimap
      • unordered_multiset

4、使用map进行单词计数程序

 1 map<string, size_t> word_count;  //string到size_t的空map
 2 string word;
 3 while(cin >> word)
 4 {
 5     ++word_count[word];//提取Word的计数器并加1
 6 }
 7 
 8 for(const auto &w : word_count)
 9 {
10     cout << w.first << " occurs " << w.second << "time(s)" << endl;
11 }

  map中元素的关键字是string类型,值是size_t类型,当对word_count进行下标操作时,可以使用一个string作下标,获得与此string相关联的size_t类型的计数器。

5、课后11.3题,自己写单词计数程序:

 1 #include<iostream>
 2 #include<string>
 3 #include<fstream>
 4 #include<vector>
 5 #include<map>
 6 using namespace std;
 7 
 8 int main(int argc, char**argv)
 9 {
10     //map的定义
11     map<string,size_t> word_count;
12     fstream in("1.txt");//定义一个输入流
13     string word;
14 
15     while (in>>word)
16     {
17         ++word_count[word];
18     }
19 
20     //map同样支持迭代器操作
21     map<string ,size_t>::iterator mapi;
22     for (mapi = word_count.begin(); mapi != word_count.end(); ++mapi)//C++ 11支持:const auto &s : word_count
23     {
24         //两个成员分别代表关键字和对应值
25         cout<<mapi->first<<" ";
26         cout<<mapi->second<<" "<<endl;
27     }
28 
29     return 0;
30 }

6、

 1 map<string, size_t> word_count;  
 2 set<string> exclude = {"the", "but"};  
 3 
 4 string word;  
 5 
 6 while(cin >> word)  
 7 {  
 8     if( exclude.find(word) == exclude.end() )  //只统计不在exclude中的单词
 9     {  
10         ++word_count[word];  //获取并递增word的计数器
11     }  
12 }  
13 
14 for(const auto &w : word_count)  
15 {  
16     cout << w.first << " occurs " << w.second << "time(s)" << endl;   
17 }

二、关联容器概述

1、map既需要关键字类型又需要指明值类型。set只需要指明关键字类型,因为set没有值。每个关联容器都定义一个默认构造函数,可以创建一个指定的空容器。既可以把关联容器初始化为另一个同类型容器的拷贝,或是从一个值范围来初始化关联容器,只要这些值能转化为容器所需的类型即可。在新标准下,可以对容器值初始化。

1 set<string> exclude = {"the", "but"};  
2 map<string, string> authors = { {"joyce", "james"},   
3     {"austen","jane"},  
4     {"dickens","charies"} };

2、multimap和multiset可以有多个元素具有相同的关键字:

 1 vector<int> ivec;  
 2 for( vector<int>::size_type i = 0; i != 10; ++i )  
 3 {  
 4     ivec.push_back(i);  
 5     ivec.push_back(i);  //每个数重复保存一次
 6     //ivec: 0 0 1 1 2 2 ...  
 7 }  
 8 
 9 set<int> iset(ivec.cbegin(), ivec.cend());  
10 multiset<int> miset(ivec.cbegin(), ivec.cend());
11 cout<<ivec.size()<<endl;//打印20
12 cout<<iset.size()<<endl; //打印10
13 cout<<miset.size()<<endl;//打印20

3、标准库使用<运算符比较两个关键字。也可以自定义一个比较操作,但必须是在关键字类型上定义一个严格弱序(strict weak ordering)。

4、对于自定义的关键字类型,我们需要提供比较函数,并且需要在定义关联容器时就指定。

  从关联容器定义的构造函数也可以看出这一点。

5、pair保存两个数据成员,定义在头文件utility中。创建一个pair时,必须提供两个类型名,pair的数据成员将具有对应的类型,两个类型不一定一样。

1 pair<string,string> anon  //两个string
2 pair<string, size_t> word_count;  //保存一个string和一个size_t
3 pair<string, vector<int>> line;  //保存string和vector<int>

可以提供初始化器:

1 pair<string,string> author{"James","Joyce"};

6、pair的数据成员是public的。分别命名为first和second。

7、对pair的返回值进行列表初始化:

1 pair<string, int>
2 process(vector<string> &v)
3 {
4     if(!v.empty())
5         return {v.back(),v.back().size()};//列表初始化
6     else    
7         return pair<string, int>();//隐式构造返回值
8 }
9 }

三、关联容器操作

1、关联容器额外的类型名

  • key_type    关键字类型
  • mapped_type    map值类型
  • value_type    对于set,与key_type相同。对于map,为pair<const key_type,mapped_type>

只有map类型才定义了mapped_type。

2、解引用关联容器的迭代器,会得到一个类型为容器的value_type的值的引用。set的关键字只能是const的。只能读,不能改。

3、遍历关联迭代器,循环打印单词计数程序:

1 //获得一个指向首元素的迭代器
2 auto map_it = word_count.cbegin();  
3 //比较当前迭代器和尾后迭代器
4 while( map_it != word_count.cend() )  
5 {  
6     //解引用迭代器,打印关键字-值对
7     cout << map_it->first << " occurs " << map_it->second << " times " << endl;  
8     ++map_it;  //递增迭代器,移动到下一个元素
9 }  

4、通常不对关联容器使用泛型算法,关键字是const这一特性意味着不能将关联容器传递给修改或重排容器元素的算法。可用于只读算法,但很多这类算法都要搜索序列。关联容器使用find成员,通过给定关键字直接获取元素。所以不使用泛型的find。

5、添加元素。insert成员可以向容器中添加一个元素或一个元素范围。

  有两个版本,接收一个迭代器或者一个初始化列表。

1 vector<int> ivec = { 2, 4, 2, 4 };  
2 set<int> set2;  
3 set2.insert( ivec.begin(), ivec.end() );  
4 set2.insert( { 1, 3, 1, 3 } );  

6、对map进行insert操作时,必须记住元素类型是pair

1 word_count.insert({word, 1});  
2 word_count.insert(make_pair(word,1));  
3 word_count.insert(pair<string,size_t>(word,1));  
4 word_count.insert(map<string,size_t>::value_type(word,1));  

7、对于不包含重复关键字的容器,insert返回一个pair。它的first成员是一个迭代器,指向具有给定关键字的元素,second成员是一个bool值,如果关键字已经在容器中,返回一个false。

用insert重写单词计数程序:

 1 map<string, size_t> word_count;  
 2 string word;  
 3 while(cin >> word)  
 4 {  
 5     //插入一个元素,若关键字等于word,则值为1
 6     //若word已在word_count中,insert什么都不做
 7     auto ret = word_count.insert({word, 1});  
 8     if(!ret.second)  
 9     {  
10         ++ret.first->second;  
11     }  
12 }  

8、对允许重复关键字的容器,insert返回一个指向新元素的迭代器。无须返回一个bool值,因为总是会插入新元素。

1 multimap<string, string> authors;  
2 authors.insert({"author1", "book1"});  
3 authors.insert({"author1", "book2"});  

9、删除元素:

  可以传递给erase一个迭代器或者迭代器对,来删除一个元素或一个元素范围。与顺序容器相类似,指定的元素被删除,函数返回void。

  提供一个额外的erase操作,接受一个key_type参数,此版本删除所有匹配给定关键字的元素,返回实际删除元素的数量。可以用此版本在打印结果之前从word_count中删除一个元素。

10、map的下标操作:

  仅map和unordered_map提供了下标操作

  与其他下标运算符不同的是:
  如果关键字不在map中,会创建一个元素插入到map中,关联值将进行值初始化

1 c[k] 若关键字不在容器中会添加   
2 c.at(k) 若关键字不在容器中会抛异常    

     map的下标操作返回的是一个mapped_type对象。

   下标运算符可能插入一个新元素,只可以对非const的map使用下标操作。

11、下标和at操作只适用于非const的map和unordered_map,且下标在元素不存在时会执行插入操作,有时这并不是我们想要的。

  关联容器提供了其他查找指定元素的方法:

  • c.find(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();

  find返回一个迭代器,指向第一个关键字为k的元素,若k不存在,则返回尾后迭代器。

  lower_bound和upper_bound不适用于无序容器。

12、如果一个multimap或multiset中有多个元素具有给定关键字,则这些关键字在容器中相邻存储。

13、如果想把具有同一关键字的所有元素都打印出来,最直观的方法是使用find和count。

 1 string search_item("Alain");    //要查找的作者
 2 auto entries = authors.count(search_item);  //元素数量
 3 auto iter = authors.find(search_item);   //此作者的第一本书
 4 //用一个循环查找此作者的所有著作
 5 while(entries)  
 6 {  
 7     cout << iter->second << endl;//打印每个题目  
 8     ++iter;  //前进到下一本书
 9     --entries;  //记录已经打印了多少书
10 }  

14、lower_bound返回的迭代器指向第一个具有给定关键词的元素,upper_bound返回的迭代器指向最后一个匹配给定关键字之后的位置。如果元素不在multimap中,则lower_bound和upper_bound返回相等的迭代器,指向一个不影响排序的关键字插入位置。因此,使用相同的关键字调用lower_bound和upper_bound会得到一个迭代器范围,表示所有具有该元素关键字的范围。如果两者返回相同的迭代器,则表示关键字不在容器中。

1 for(auto beg = authors.lower_bound(search_item),
2 end = authors.upper_bound(search_item);
3 beg != end; ++beg)  
4 {  
5     cout << beg->second << endl;  
6 } 

15、equal_range函数:接受一个关键字,返回一个迭代器pair,若关键字存在,则第一个迭代器指向第一个与关键字匹配的元素,第二个迭代器指向最后一个匹配元素之后的位置。若未找到匹配元素,则两个迭代器都指向关键字可插入的位置。

1 //pos保存迭代器时,表示与关键字匹配的元素范围
2 for(auto pos = authors.equal_range(search_item); pos.first != pos.second; ++pos.first)  
3 {  
4     cout << pos.first->second << endl;  
5 }  

四、无序容器

1、有序容器通过比较运算符来组织元素,无序容器通过哈希函数和==运算符来组织元素。

2、可以使用无序容器替换对应的有序容器,但由于元素未按顺序存储,一个使用无序容器的程序的输出通常与有序容器不同。

用unorder_map重写最初的单词计数程序:输出单词的顺序不会按照字典排序

  unorder_map<string, size_t> word_count;  //string到size_t的空map
  string word;
  while(cin >> word)
 {
     ++word_count[word];//提取Word的计数器并加1
  }  
  
  for(const auto &w : word_count)
  {
     cout << w.first << " occurs " << w.second << "time(s)" << endl;
 }

3、为了访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个桶。

  哈希函数:将给定类型的值映射到一个整形值的函数,相同的值必须映射到相同的整数,不同的值尽可能映射到不同的整数。

  如果容器允许重复关键字,则所有具有相同关键字的元素在同一个桶中。无序容器的性能依赖于哈希函数的质量和桶的数量和大小。

  如果一个桶中保存了很多元素,需要顺序搜索这些元素来查找我们需要的那个,那么查找一个特定元素就需要大量比较操作。

4、无序容器管理操作:

桶接口

  • 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.rehash(n)
  • c.reserve(n)  重新存储,使得c可以保存n个元素且不必rehash

5、类似于为有序容器提供比较操作,我们需要为无序容器提供hash值计算函数和相等判断函数。

附:一个单词转换的map:

给定一个string,转化为另一个string。输入两个文件,第一个保存一些规则,用来转换第二个文件中的文本。每条规则由两个部分组成:一个可能出现在输入文件中的单词和一个用来替换他的短语。每当一个单词出现时,就把他替换成对应的短语。第二个文件包含要转换的文本。

  3.20:下午2.30开会到6.30。师兄们汇报研究成果,给我们分配任务。要求暑假前出文章。亚历山大。

  3.21:大四毕设要做DPC的相关,之前没做过这方面。给他整理了之前参考资料。源码也可以给他,本科毕业应该不难吧。别太烦我就行,指导反而更费时间。

      P391,一个单词替换的map还没有看,很有用的一个应用。

        第十一章结束,且随疾风前行。

原文地址:https://www.cnblogs.com/zlz099/p/6589361.html