C ++ Primer(第五版)第十一章练习答案
11.1 节练习
练习 11.1
描述 map 和 vector 的不同。
map 是关联容器,称为关联数组,其元素是按关键字-值对来保存和访问的,它的关键字是元素的下标;
vector 是顺序容器,其元素是按它们在容器中的位置来顺序保存和访问的,它的下标一定是数字。
练习 11.2
分别给出最适合使用 list、vector、deque、map 以及 set 的例子。
list:需要多次在除首位位置插入元素;
vector:需要随机访问元素;
deque:需要随机访问元素,同时需要在首位增删元素;
map:保存具有映射的元素对,例如字典;
set:保存需要用来判断的元素集合,元素本身是唯一的关键字。
练习 11.3
编写你自己的单词计数程序。
#include<iostream>
#include<map>
#include<string>
using std::cin;
using std::cout;
using std::endl;
using std::map;
using std::string;
int main()
{
map<string, size_t> word_count;
string word;
while (cin>>word)
{
++word_count[word];
}
for(const auto &w:word_count)
{
cout << w.first << " 出现了 " << w.second << " 次!" << endl;
}
return 0;
}
练习 11.4
扩展你的程序,忽略大小写和标点。例如,“example.”、“example,” 和 “Example” 应该递增相同的计数器。
#include<iostream>
#include<map>
#include<set>
#include<string>
#include<algorithm>
using namespace std;
int main()
{
map<string, size_t> word_count;
string word;
while (cin>>word)
{
// 转换为小写
transform(word.begin(), word.end(), word.begin(), ::tolower);
auto iter = word.end() - 1;
if (ispunct(*iter))
{
iter = word.erase(iter);
}
++word_count[word];
}
for(const auto &w:word_count)
{
cout << w.first << " 出现了 " << w.second << " 次!" << endl;
}
return 0;
}
11.2.1 节练习
练习 11.5
解释 map 和 set 的区别。你如何选择使用哪个?
map包括关键字-值对;
set只有关键字。
练习 11.6
解释 set 和 list 的区别。你如何选择使用哪个?
set:是关联容器,查找较快,但不支持位置相关的操作;
list:是顺序容器,查找关键字是和容器的大小有关系,支持位置相关的操作,功能更多。
练习 11.7
定义一个 map,关键字是家庭的姓,值是一个 vector,保存家中孩子(们)的名。编写代码,实现添加新的家庭以及向已有家庭中添加新的孩子。
#include<iostream>
#include<vector>
#include<string>
#include<map>
using namespace std;
int main()
{
map<string, vector<string>> family;
string lName, fName;
while (cin>> lName >> fName)
{
family[fName].push_back(lName);
}
for(const auto &f:family)
{
cout << f.first << " 家中有:" << endl;
for(const auto &n:f.second)
{
cout << n << " ";
}
cout << endl;
}
return 0;
}
练习 11.8
编写一个程序,在一个 vector 而不是一个 set 中保存不重复的单词。使用 set 的优点是什么?
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
using namespace std;
int main()
{
vector<string> sv;
string str;
while (cin>>str)
{
if (find(sv.begin(),sv.end(),str)==sv.end())
{
sv.push_back(str);
}
}
for(const auto &s : sv)
{
cout << s << " ";
}
cout << endl;
return 0;
}
vector 需要判断输入是否重复;set 不需要。
11.2.2 节练习
练习 11.9
定义一个 map,将单词与一个行号的 list 关联,list 中保存的是单词所出现的行号。
map<string,list<size_t>> m
练习 11.10
可以定义一个 vector::iterator 到 int 的 map 吗?list::iterator 到 int 的 map 呢?对于两种情况,如果不能,解释为什么。
前者可以,后者不行。因为 vector 的迭代器支持“严格弱序”操作,即“ < ”,而 list 的迭代器不支持。
练习 11.11
不使用 decltype 重新定义 bookstore。
multiset<Sales_data, bool (*pf)(const Sales_data &lhs, const Sales_data &rhs)> bookstore(compareIsbn);
11.2.3 节练习
练习 11.12
编写程序,读入 string 和 int 的序列,将每个 string 和 int 存入一个 pair 中,pair 保存在一个 vector 中。
#include<iostream>
#include<vector>
#include<string>
#include<utility>
using namespace std;
int main()
{
string str;
int i;
vector<pair<string, int>> pv;
while (cin>>str>>i)
{
pv.push_back(pair<string, int>(str, i));
}
for(const auto &p:pv)
{
cout << p.first << " " << p.second << endl;
}
return 0;
}
练习 11.13
在上一题的程序中,至少有三种创建 pair 的方法。编写此程序的三个版本,分别采用不同的方法创建 pair。解释你认为哪种形式最易于编写和理解,为什么?
#include<iostream>
#include<vector>
#include<string>
#include<utility>
using namespace std;
int main()
{
string str;
int i;
vector<pair<string, int>> pv;
while (cin>>str>>i)
{
// 使用成员进行初始化
pv.push_back(pair<string, int>(str, i));
// 直接隐式调用默认构造函数转化为pair
// pv.push_back({str, i});
// 调用make_pair函数进行构造
// pv.push_back(make_pair(str, i));
// 使用成员进行初始化
// pair<string, int> p(str, i);
// pv.push_back(p);
// 使用成员进行初始化
// pair<string, int> p = {str, i};
// pv.push_back(p);
}
for(const auto &p:pv)
{
cout << p.first << " " << p.second << endl;
}
return 0;
}
直接隐式构造最利于编写,指定成员类型并进行初始化最易于理解。
练习 11.14
扩展你在 11.2.1节练习(第378页)中编写的孩子姓到名的 map,添加一个 pair 的 vector,保存孩子的名和生日。
#include<iostream>
#include<vector>
#include<string>
#include<map>
#include<sstream>
#include<utility>
using namespace std;
int main()
{
//map<string, vector<string>> family;
map<string, vector<pair<string, string>>> family;
string lName, fName, birthday;
while (cin>> lName >> fName>>birthday)
{
family[fName].push_back({lName, birthday});
}
for(const auto &f:family)
{
cout << f.first << " 家中有:" << endl;
for(const auto &n:f.second)
{
cout << n.first << " 生日是:" << n.second << endl;
}
cout << endl;
}
return 0;
}
11.3.1 节练习
练习 11.15
对一个 int 到 vector< int > 的 map,其 mapped_type、key_type 和 value_type 分别是什么?
mapped_type:vector< int >
key_type:int
value_type:pair<const int, vector< int >>
练习 11.16
使用一个 map 迭代器编写一个表达式,将一个值赋予一个元素。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
map<int, string> sm{{1, "one"}};
map<int, string>::iterator iter = sm.begin();
iter->second = "two";
for(const auto &n:sm)
{
cout << n.first << " " << n.second << endl;
}
return 0;
}
练习 11.17
假定 c 是一个 string 的 multiset,v 是一个 string 的 vector,解释下面的调用。指出每个调用是否合法:
copy(v.begin(), v.end(), inserter(c, c.end()));
copy(v.begin(), v.end(), back_inserter(c));
copy(c.begin(), c.end(), inserter(v, v.end()));
copy(c.begin(), c.end(), back_inserter(v));
(1)合法。将 v 中的元素依次插入到 c 的尾部;
(2)不合法。back_inserter 需要调用 push_back() 关联容器不支持;
(3)和(4)合法。将 c 中的元素依次插入 v 的尾部;
练习 11.18
写出第 382 页循环中 map_it 的类型,不要使用 auto 或 decltype。
map<string, size_t>::iterator
练习 11.19
定义一个变量,通过对 11.2.2 节(第 378 页)中的名为 bookstore 的 multiset 调用 begin() 来初始化这个变量。写出变量的类型,不要使用 auto 或 decltype。
multiset<Sales_data, bool (*pf)(const Sales_data &lhs, const Sales_data &rhs)>:: iterator bookstore_iter = bookstore.begin();
11.3.2 节练习
练习 11.20
重写 11.1 节练习(第 376 页)的单词计数程序,使用 insert 代替下标操作。你认为哪个程序更容易编写和阅读?解释原因。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
map<string, size_t> word_count;
string word;
while (cin>>word)
{
// ++word_count[word];
auto ret = word_count.insert({word, 1});
if (!ret.second)
{
++(ret.first->second);
}
}
for(const auto &w:word_count)
{
cout << w.first << " 出现了 " << w.second << " 次!" << endl;
}
return 0;
}
下标版本更容易编写和理解。insert 版本还要手动判断插入元素是否已经存在。
练习 11.21
假定 word_count 是一个 string 到 size_t 的 map,word 是一个 string,解释下面循环的作用:
while (cin >> word)
++word_count.insert({word, 0}).first->second;
insert 插入 word,并将它的计数值置为 0,返回一个 pair,pair 的 first 是插入元素的迭代器,迭代器指向一个 pair,pair 的 second 是这个 word 对应的计数值,即 0,最后将其自增结果为 1,表示插入元素并且计数为 1。
练习 11.22
给定一个 map<string,vector< int >>,对此容器的插入一个元素的 insert 版本,写出其参数类型和返回类型。
参数类型:
pair <string, vector< int >>
返回类型:
pair <map<string, vector< int >>::iterator, bool>
练习 11.23
11.2.1 节练习(第 378 页)中的 map 以孩子的姓为关键字,保存他们的名的 vector,用 multimap 重写此 map。
#include<iostream>
#include<vector>
#include<string>
#include<map>
using namespace std;
int main()
{
multimap<string, vector<string>> family;
string lName, fName;
while (cin>> lName >> fName)
{
vector<string> lNames;
lNames.push_back(lName);
family.insert({fName, lNames});
}
for(const auto &f:family)
{
cout << f.first << " 家中有:" << endl;
for(const auto &n:f.second)
{
cout << n << " ";
}
cout << endl;
}
return 0;
}
11.3.4 节练习
练习 11.24
下面的程序完成什么功能?
map<int, int> m;
m[0] = 1;
检查 m 中是否有关键字为 0 的键-值对:如果有,将它的值赋值为1;如果没有,添加关键字 0,并赋值为1.
练习 11.25
对比下面的程序与上一题程序
vector<int> v;
v[0] = 1;
没有初始化,不能自动插入元素。
练习 11.26
可以用什么类型来对一个 map 进行下标操作?下标运算符返回的类型是什么?请给出一个具体例子——即,定义一个 map,然后写出一个可以用来对 map 进行下标操作的类型以及下标运算符将会返会的类型。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
map<int, string> ism{{0,"a"}};
int i = 0;
string str = ism[i];
cout << str << endl;
map<int, string>::key_type j = 0;
map<int, string>::mapped_type mstr = ism[j];
cout << mstr << endl;
return 0;
}
11.3.5 节练习
练习 11.27
对于什么问题你会使用 count 来解决?什么时候你又会选择 find 呢?
要检查特定元素的关键字是否在容器中用 find;
如果是要统计指定元素的关键字的数量用 count。
练习 11.28
对一个 string 到 int 的 vector 的 map,定义并初始化一个变量来保存在其上调用 find 所返回的结果。
#include<iostream>
#include<string>
#include<vector>
#include<map>
using namespace std;
int main()
{
map<string, vector<int>> s_ivm = {{"a", {1, 1, 1, 1}}};
map<string, vector<int>>::iterator iter = s_ivm.find("a");
cout << (iter->second)[0] << endl;
return 0;
}
练习 11.29
如果给定的关键字不在容器中,upper_bound、lower_bound 和 equal_range 分别会返回什么?
如果关键词不存在,upper_bound 和 lower_bound 的返回会指向同一个迭代器;equal_range 的返回迭代器 pair 中的两个迭代器会相等。
练习 11.30
对于本节最后一个程序中的输出表达式,解释运算对象 pos.first->second 的含义。
pos 保存迭代器对,表示与关键字匹配的元素范围;
pos.first 是匹配范围的开头迭代器,指向一个 mapped_value;
pos.first->second 是解引用键值对中的值。
练习 11.31
编写程序,定义一个作者及其作品的 multimap。使用 find 在 multimap 中查找一个元素并用 erase 删除它。确保你的程序在元素不在 map 中时也能正常运行。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
multimap<string, string> ssmm = {{"Mike", "aaa"}, {"Jone", "asas"}, {"Mike", "bbb"}};
ssmm.erase(ssmm.find("Jone"));
for(const auto &m:ssmm)
{
cout << m.first << " " << m.second << endl;
}
return 0;
}
练习 11.32
使用上一题定义的 multimap 编写一个程序,按字典序打印作者列表和他们的作品。
#include<iostream>
#include<map>
#include<string>
#include<set>
using namespace std;
int main()
{
multimap<string, string> ssmm = {{"Mike", "bbb"}, {"Mike", "aaa"}, {"Jone", "asas"}};
map<string, set<string>> authors;
for(const auto a:ssmm)
{
authors[a.first].insert(a.second);
}
for(const auto &m:authors)
{
cout << m.first << ":";
for(const auto & a:m.second)
{
cout << a << " ";
}
cout << endl;
}
return 0;
}
11.3.6 节练习
练习 11.33
实现你自己版本的单词转换程序。
#include<iostream>
#include<string>
#include<map>
#include<fstream>
#include<sstream>
using namespace std;
map<string, string> buildMap(ifstream &map_file)
{
map<string, string> trans_map;
string key;
string value;
while (map_file>>key&&getline(map_file,value))
{
if (value.size() > 1)
{
trans_map[key] = value.substr(1);
}
else
{
throw runtime_error("没有对 " + key + "的转换");
}
}
return trans_map;
}
const string & transform(const string &s, const map<string, string> &m)
{
auto map_it = m.find(s);
if (map_it!=m.cend())
{
return map_it->second;
}
else
{
return s;
}
}
void word_transform(ifstream &map_file, ifstream &input)
{
auto trans_map = buildMap(map_file);
string text;
while (getline(input,text))
{
istringstream stream(text);
string word;
bool firstword = true;
while (stream>>word)
{
if (firstword)
{
firstword = false;
}
else
{
cout << " ";
}
cout << transform(word, trans_map);
}
cout << endl;
}
}
int main()
{
ifstream word_file("word_transform_rule.txt"), input("word_text.txt");
word_transform(word_file, input);
return 0;
}
练习 11.34
如果你将 transform 函数中的 find 替换为下标运算符,会发生什么情况?
遇到转换规则里没有的单词会插入到规则 map 中,并初始化为空。
练习 11.35
在 buildMap 中,如果进行如下改写,会有什么效果?
trans_map[key] = value.substr(1);
改为trans_map.insert({key, value.substr(1)});
本程序中没有影响,如果关键字有多次出现,下标操作会将保留最后一次出现的值;而 insert 操作只会保留第一次的值。
练习11.36
我们的程序并没检查输入文件的合法性。特别是,它假定转换规则文件中的规则都是有意义的。如果文件中的某一行包含一个关键字、一个空格,然后就结束了,会发生什么?预测程序的行为并进行验证,再与你的程序进行比较。
if (value.size() > 1)
{
trans_map[key] = value.substr(1);
}
else
{
throw runtime_error("没有对 " + key + "的转换");
}
只有一个空格,则 value.size() =1 会抛出异常!
11.4 节练习
练习 11.37
一个无序容器与其有序版本相比有何优势?有序版本有何优势?
无序容器能获得更好的平均性能;
有序容器可以定义元素排序。
练习 11.38
用 unordered_map 重写单词计数程序(参见 11.1 节,第 375 页)和单词转换程序(参见 11.3.6 节,第 391 页)。
#include<iostream>
#include<unordered_map>
#include<string>
using namespace std;
int main()
{
unordered_map<string, size_t> word_count;
string word;
while (cin>>word)
{
++word_count[word];
}
for(const auto &w:word_count)
{
cout << w.first << " " << w.second << endl;
}
return 0;
}
#include<iostream>
#include<string>
#include<unordered_map>
#include<fstream>
#include<sstream>
using namespace std;
unordered_map<string, string> buildunordered_map(ifstream &unordered_map_file)
{
unordered_map<string, string> trans_unordered_map;
string key;
string value;
while (unordered_map_file>>key&&getline(unordered_map_file,value))
{
if (value.size() > 1)
{
trans_unordered_map[key] = value.substr(1);
}
else
{
throw runtime_error("没有对 " + key + "的转换");
}
}
return trans_unordered_map;
}
const string & transform(const string &s, const unordered_map<string, string> &m)
{
auto unordered_map_it = m.find(s);
if (unordered_map_it!=m.cend())
{
return unordered_map_it->second;
}
else
{
return s;
}
}
void word_transform(ifstream &unordered_map_file, ifstream &input)
{
auto trans_unordered_map = buildunordered_map(unordered_map_file);
string text;
while (getline(input,text))
{
istringstream stream(text);
string word;
bool firstword = true;
while (stream>>word)
{
if (firstword)
{
firstword = false;
}
else
{
cout << " ";
}
cout << transform(word, trans_unordered_map);
}
cout << endl;
}
}
int main()
{
ifstream word_file("word_transform_rule.txt"), input("word_text.txt");
word_transform(word_file, input);
return 0;
}