《C++ Primer》第17章 标准库特殊设施
导读本章介绍了tuple、bitset、正则表达式、随机数和特殊的IO操作。
本章的练习帮助读者熟悉这些标准库设施的使用。
17.1节 tuple类型 习题答案
练习17.1:定义一个保存三个int值的tuple,并将其成员分别初始化为10、20和30。
【出题思路】
本题练习定义tuple。
【解答】
注意只能直接初始化。
tuple<int, int, int> ti{10, 20, 30}
练习17.2:定义一个tuple,保存一个string、一个vector<string>和一个pair<string, int>。
【出题思路】
本题练习定义tuple。
【解答】
tuple<string, vector<string>, pair<string, int>> t;
练习17.3:重写12.3节(第430页)中的TextQuery程序,使用tuple代替QueryResult类。你认为哪种设计更好?为什么?
【出题思路】
在较大的例子中练习定义和使用tuple。
【解答】
首先修改TextQuery的定义:
1.不再包含QueryResult.h,而是包含tuple头文件。
2.将line_no、line_it和print的声明从QueryResult.h拷贝到t_TextQuery.h中。
3.将QueryResult定义为一个tuple类型而不再是一个类,tuple的三项分别是原QueryResult类的数据成员。
#include <tuple>
typedef std::vector<std::string>::size_type line_no;
typedef std::set<line_no>::const_iterator line_it;
typedef std::tuple<std::string, std::shared_ptr<std::set<line_no>>,
std::shared_ptr<std::vector<std::string>>> QueryResult;
//这个声明是必需的,查询函数中需返回QueryResult类型
class TextQuery {
public:
TextQuery(std::ifstream&);
QueryResult query(const std::string&) const;
void display_map();//调试辅助函数:打印映射表
private:
std::shared_ptr<std::vector<std::string>> file;//输入文件
//将每个单词映身到它出现的行号的集合
std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;
//规范文本:删除标点,并转换为小写
static std::string cleanup_str(const std::string&);
};
std::ostream &print(std::ostream, const QueryResult&);
然后修改TextQuery.cpp:
1.TextQuery::query成员函数虽然返回QueryResult,但其代码其实不必修改,因为这里构造QueryResult使用的是直接初始化方式,改为tuple类型后,初始化语句的形式和原来完全一致。2.主要修改print,只有它直接访问了QueryResult的成员。对QueryResult对象qr,原来通过qr.sought、qr.lines的形式访问检索词和搜索到的行号集合,现在改为get<0>(qr)、get<1>(qr)这样的形式即可。
ostream &print(ostream &os, const QueryResult &qr)
{
//如果找到了单词,打印出现次数及所有出现的行号
os << get<0>(qr) << " occurs " << get<1>(qr)->size() << " "
<< make_plural(get<1>(qr)->size(), "time", "s") << endl;
//打印单词出现的每一行
for(auto num:*get<1>(qr)) //对set中每个元素
//不让用户对从0开始的文本行号困惑
os << "\t(line " << num + 1 << ")"
<< *(get<2>(qr)->begin() + num) << endl;
return os;
}
可以看到,修改比较简单,而使用tuple的代码也比定义一个类更为简单。若查询结果只是临时使用,比如输出后即丢弃,则使用tuple是一种简单有效的方式。否则,若查询结果还要用来进行其他处理,定义QueryResult类是更好的方式。
练习17.4:编写并测试你自己版本的findBook函数。
【出题思路】
本题练习用tuple返回多个值。
【解答】
参考书中本节内容编写即可,配套网站上有完整代码供对照。编写主程序测试findBook。
int main(int argc, char **argv)
{
assert(argc > 1);
//文件中每个元素 保存一个特定书店的销售记录
vector<vector<Sales_data>> files;
for(int cnt = 1; cnt != argc; ++cnt)
files.push_back(build_store(argv[cnt]));
ifstream in("findbook.in");//要搜索的ISBN号
reportResults(in, cout, files);
return 0;
}
它调用build_store来读取命令行参数中指出的书店销售记录文件,存到files中。然后通过reportResults调用findBook来查找指定书目的销售记录。build_store如下:
vector<Sales_data> build_store(const string &s)
{
Sales_data item;
vector<Sales_data> ret;
ifstream is(s);
while(read(is, item))
ret.push_back(item);
sort(ret.begin(), ret.end(), it); //equal_range要求序列排好序
return ret;
}
注意,为了正确执行findBook,build_store按书目的isbn进行了排序,其中lt比较两个Sales_data的isbn。
练习17.5:重写findBook,令其返回一个pair,包含一个索引和一个迭代器pair。
【出题思路】
本题练习tuple替代解决方案。
【解答】
相对于tuple版,主要修改:
1.matches的类型改为pair,其中第二个成员还是一个pair,保存书目的起止迭代器。
2.findBook中构造返回值的部分,用make_pair构造一个pair,第一个参数与tuple版本一样,第二个参数直接用found。
3.reportResults从findBook返回的matches中提取数据,进行输出的部分。get<0>(store)、get<1>(store)和get<2>(store)改为store.first、store.second.first和store.second.second。
typedef pair<vector<Sales_data>::size_type,
pair<vector<Sales_data>::const_iterator,
vector<Sales_data>::const_iterator>> matches;
ret.push_back(make_pair(it - files.cbegin(), found));
os << "store " << store.first << " sales: "
<< accumulate(store.second.first, store.second.second, Sales_data(s))
<< endl;
练习17.6:重写findBook,不使用tuple或pair。
【出题思路】
本题练习定义类代替tuple方式。
【解答】
首先定义matches类,它有一个size_type成员和两个迭代器成员,构造函数接受一个size_type参数和一个pair参数来初始化3个成员,另外定义3个成员函数分别获取3个数据成员。
class matches{
public:
matches(vector<Sales_data>::size_type n,
pair<vector<Sales_data>::const_iterator,
vector<Sales_data>::const_iterator> f)
:num(n), first(f.first), last(f.second)
{ }
vector<Sales_data>::size_type get_num() const
{ return num; }
vector<Sales_data>::const_iterator get_first() const
{ return first; }
vector<Sales_data>::const_iterator get_last() const
{ return last; }
private:
vector<Sales_data>::size_type num;
vector<Sales_data>::const_iterator first, last;
};
然后修改reportResults中访问3个数据的方法:
os << "store " << store.get_num() << " sales: "
<< accumulate(store.get_first(), store.get_last(), Sales_data(s))
<< endl;
练习17.7:解释你更倾向于哪个版本的findBook,为什么。
【出题思路】
理解pair、tuple和类实现的差别。
【解答】
对于本题,只是简单使用搜索结果,pair和tuple都是简单直接的实现方式。若搜索结果还需进行复杂的计算、处理,定义一个类对其进行封装更好。
练习17.8:在本节最后一段代码中,如果我们将Sales_data()作为第三个参数传递给accumulate,会发生什么?
【出题思路】
复习Sales_data的构造函数的使用。
【解答】
Sales_data()是Sales_data的默认构造函数,对所有数据成员都采用值初始化,因此isbn被初始化为空字符串。因此,在输出结果中,将看不到书目的isbn。