笔记:
11.1 关联容器
类型map和multimap定义在头文件map中,set和multiset定义在头文件set中;无序容器则定义在unordered_map和unordered_set中。
11.2 关联容器概述
11.3 关联容器操作
我们通常不对关联容器使用泛型算法。
解引用关联容器的迭代器得到的是value_type的值的引用。
关联容器map迭代器的类型是pair类型,记得pair类型的第一个类型是const,即map关键字类型是const。
pair类型定义在utility头文件中。
insert和下标运算符的区别:参考例题11.35。
下标运算符,只能用于map和unordered_map类型的非const对象。
总结:
关联容器支持通过关键字高效查找和提取元素。对关键字的使用将关联容器和顺序容器区分开来,顺序容器是通过位置访问元素的。
课后习题:
练习 11.1:描述map 和vector 的不同。
答:两类容器的根本差别在于,顺序容器中的元素是“顺序”存储的(链表容器中的元素虽然不是在内存中“连续”存储的,但仍然是按“顺序”存储的。)理解“顺序”的关键,是理解容器支持的操作形式以及效率。
对于vector这样的顺序容器,元素在其中按顺序存储,每个元素都有唯一对应的位置编号,所有操作都是按编号(位置)进行的。例如,获取元素,插入删除元素,遍历元素。底层的数据结构是数组、链表,简单但能保证上述操作的高效。对于依赖值的元素访问,例如查找给定值(find),在这种数据结构上的实现是要通过遍历完成的,效率不佳。
而map这种关联容器,就是为了高效实现“按值访问元素”这类操作而设计的。为了达到这一目的,容器中的元素是按关键字值存储的,关键字值与元素数据建立起对应关系,这就是“关联”的含义。底层数据结构是红黑树、哈希表等,可高效实现按关键字值查找、添加、删除元素等操作。
练习 11.2:分别给出最适合使用list 、vector、deque 、map 以及set 的例子。
答:若元素很小(例如int),大致数量预先可知,在程序运行过程中不会剧烈变化,大部分情况下只在末尾添加或删除需要频繁访问任意位置的元素,则vector可带来最高的效率。若需要频繁在头部和尾部添加或删除元素,则deque是最好的选择。
如果元素较大(如大的类对象),数量预先不知道,或是程序运行过程中频繁变化,对元素的访问更多是顺序访问全部或很多元素,则list很适合。
map适合对一些对象按它们的某个特征进行访问的情形。典型的例如按学生的名字来查询学生信息,即可将学生名字作为关键字,将学生信息作为元素值,保存在map中。
set就是集合类型,当需要保存特定的值集合(通常是满足/不满足某种要求的值的集合),用set最为方便。
练习 11.3:编写你自己的单词计数程序。
//练习 11.3
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <set>
#include <cstddef> //size_t类型定义的头文件,sizt_t是与机器无关的无符号数
using namespace std;
int main(int argc, char *argv[])
{
ifstream in(argv[1]);
if (!in)
{
cout << "打开输入文件失败!" << endl;
exit(1);
}
//从string到size_t的空map
map<string, size_t> word_count;
//不统计的单词的集合
set<string> exclude = { "The", "But", "And", "Or", "An", "A",
"the", "but", "and", "or", "an", "a" };
string word;
while (in >> word)
{
//单词不在不统计的集合里时,才统计
if (exclude.find(word) == exclude.end())
{
++word_count[word];
}
}
for (const auto &w : word_count)
cout << w.first << " occurs " << w.second
<< ((w.second > 1) ? " times" : " time") << endl;
system("pause");
return 0;
}
练习 11.4:扩展你的程序,忽略大小写和标点。例如, ”example.”、”example,和”Example”,应该递增相同的计数器。
//练习 11.4
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <set>
#include <cstddef> //size_t类型定义的头文件,sizt_t是与机器无关的无符号数
using namespace std;
//编写一个函数,将单词中的大写全部转化为小写,标点符号全部去掉
string &trans(string &s)
{
for (int p = 0; p < s.size(); p++)
{
if (s[p] >= 'A' && s[p] <= 'Z')
{
s[p] -= ('A' - 'a');
}
else if (s[p] == ',' || s[p] == '.')
{
s.erase(p, 1);
}
}
return s;
}
int main(int argc, char *argv[])
{
ifstream in(argv[1]);
if (!in)
{
cout << "打开输入文件失败!" << endl;
exit(1);
}
//从string到size_t的空map
map<string, size_t> word_count;
//不统计的单词的集合
set<string> exclude = { "The", "But", "And", "Or", "An", "A",
"the", "but", "and", "or", "an", "a" };
string word;
while (in >> word)
{
trans(word);
//单词不在不统计的集合里时,才统计
if (exclude.find(word) == exclude.end())
{
++word_count[word];
}
}
for (const auto &w : word_count)
cout << w.first << " occurs " << w.second
<< ((w.second > 1) ? " times" : " time") << endl;
system("pause");
return 0;
}
练习 11.5:解释map 和set 的区别。你如何选择使用哪个?
答:当需要查找给定值所对应的数据时,应使用map,其中保存的是<关键字,值>对,按关键字访问值。
如果只需判定给定值是否存在时,应使用set,它只是简单的值的集合。
练习 11.6:解释set 和list 的区别。你如何选择使用哪个?
答:两者都可以保存元素集合。如果只需要顺序访问这些元素,或是按位置访问元素,那么应使用list。如果需要快速判定是否有元素等于给定值,则应使用set。
练习 11.7:定义一个map,关键字是家庭的姓,值是一个vector,保存家中孩子(们)的名。编写代码,实现添加新的家庭以及向己有家庭中添加新的孩子。
//练习 11.7
#include <iostream>
#include <string>
#include <vector>
#include <map>
using namespace std;
//如果新添加的家庭不在map中,则才添加这个新家庭
void add_family(map<string, vector<string>> &families, const string &family)
{
if (families.find(family) == families.end())
{
families[family] = vector<string>();
}
}
//向已有的家庭中添加新的孩子
void add_child(map<strin