C ++ Primer(第五版)第十二章练习答案
12.1.1 节练习
练习 12.1
在此代码的结尾,b1 和 b2 各包含多少个元素?
StrBlob b1;
{
StrBlob b2 = {"a", "an", "the"};
b1 = b2;
b2.push_back("about");
}
b1 包含 4 个元素;
b2 被销毁。
练习 12.2
编写你自己的 StrBlob 类,包含 const 版本的 front 和 back。
#ifndef STRBLOB_H_
#define STRBLOB_H_
#include<initializer_list>
#include<vector>
#include<string>
#include<memory>
class StrBlob
{
public:
typedef std::vector<std::string>::size_type size_type;
StrBlob();
StrBlob(std::initializer_list<std::string> il);
size_type size() const { return data->size(); }
bool empty() const {return data->empty()};
void push_back(const std::string &t) { data->push_back(t); }
void pop_back();
std::string &front();
std::string &back();
private:
std::shared_ptr<std::vector<std::string>> data;
void check(size_type i, const std::string &msg) const;
};
StrBlob::StrBlob():data(make_shared<std::vector<std::string>>()){}
StrBlob::StrBlob(std::initializer_list<std::string> il):data(make_shared<std::vector<std::string>>(il)){}
void StrBlob::check(size_type i, const std::string &msg) const
{
if (i>=data->size())
{
throw out_of_range(msg);
}
}
std::string& StrBlob::front()
{
check(0, "front on empty StrBlob");
return data->front();
}
std:string &StrBlob::back()
{
check(0, "back on empty StrBlob");
return data->back();
}
void StrBlob::pop_back()
{
check(0, "pop_back on empty StrBlob");
data->pop_back();
}
#endif
练习 12.3
StrBlob 需要 const 版本的 push_back 和 pop_back 吗?如果需要,添加进去。否则,解释为什么不需要。
可以,但没有必要。
StrBlob 类的数据成员是一个智能指针,当使用 const 版本时,只是将指针本身固定,但指针所指向的对象可以是改变的,所以,可以使用 const 版本的 push_back 和 pop_back。
看待这个问题,最重要的是要站在类的使用者的角度来看,而不是类的设计者的角度。
虽然在类的具体实现中,数据成员是一个指向 vector< string> 的智能指针;但由于类的封装,在类的使用者看来,数据成员是 vector< string>,他们并不知道具体的实现使用了智能指针。
那么当类的使用者声明类的常量对象时,他们期待的结果是vector< string>的内容不会被改变。所以我们在设计这个类的时候,要考虑到类的使用者的真实意图,对于像 push_back 和 pop_back 这样会改变智能指针所指向的 vector< string> 内容的成员函数,我们不应该声明和定义成 const 版本。这样在类的使用者使用类的常对象时,就不能调用 push_back 和 pop_back 成员函数,不能改变智能指针所指向的 vector< string>的内容了,这正好与类的使用者的意图相符。解释来源
练习 12.4
在我们的 check 函数中,没有检查 i 是否大于0。为什么可以忽略这个检查?
因为 size_type 是无符号类型,一定大于等于 0。
练习 12.5
我们未编写接受一个 initializer_list explicit 参数的构造函数。讨论这个设计策略的优点和缺点。
优点:可以隐式转换数据类型;
缺点:转换会耗费资源。
12.1.2 节练习
练习 12.6
编写函数,返回一个动态分配的 int 的vector。将此 vector 传递给另一个函数,这个函数读取标准输入,将读入的值保存在 vector 元素中。再将 vector 传递给另一个函数,打印读入的值。记得在恰当的时刻 delete vector。
#include<iostream>
#include<vector>
using namespace std;
vector<int>* create()
{
return new vector<int>;
}
void write(vector<int>*p)
{
int i;
while (cin>>i)
{
p->push_back(i);
}
}
void read(vector<int>*p)
{
for(const auto i:(*p))
{
cout << i << " ";
}
cout << endl;
}
int main()
{
auto p = create();
write(p);
read(p);
delete p;
return 0;
}
练习 12.7
重做上一题,这次使用 shared_ptr 而不是内置指针。
#include<iostream>
#include<vector>
#include<memory>
using namespace std;
shared_ptr<vector<int>> create()
{
return make_shared<vector<int>>();
}
void write(shared_ptr<vector<int>> p)
{
int i;
while (cin>>i)
{
p->push_back(i);
}
}
void read(shared_ptr<vector<int>> p)
{
for(const auto i:(*p))
{
cout << i << " ";
}
cout << endl;
}
int main()
{
auto p = create();
write(p);
read(p);
return 0;
}
练习 12.8
下面的函数是否有错误?如果有,解释错误原因。
bool b()
{
int* p = new int;
// ...
return p;
}
函数结果将指针转换为 bool 类型,但动态内存没有释放。
练习 12.9
解释下面代码执行的结果。
int *q = new int(42), *r = new int(100);
r = q;
auto q2 = make_shared<int>(42), r2 = make_shared<int>(100);
r2 = q2;
r 指向 q 指向的对象后,r 原指向的动态内存没有被释放;
r2 指向 q2 指向的对象后,由于 r2 是智能指针,当 r2 原指向的对象没有指针指向时,会自动释放动态内存。
12.1.3 节练习
练习 12.10
下面的代码调用了第 413 页中定义的 process 函数,解释此调用是否正确。如果不正确,应如何修改?
shared_ptr<int> p(new int(42));
process(shared_ptr<int>(p));
正确。
参数使用 shared_ptr< int > ( p ),返回一个 p 的临时拷贝,当调用函数时,p 的计数器将增加到 2,而调用结束时,释放临时指针,p 的计数器会减回 1。
练习 12.11
如果我们像下面这样调用 process,会发生什么?
process(shared_ptr<int>(p.get()));
使用 get 初始化智能指针时,传递的是内置指针,这样初始化的新智能指针与原智能指针 p 是相互独立的,他们的计数器都为 1;
当函数调用结束时,临时拷贝会删除,此时临时的计数器减为 0,会释放它指向的内存,而 p 指针会变成一个空悬指针。
练习 12.12
p 和 q 的定义如下,对于接下来的对 process 的每个调用,如果合法,解释它做了什么,如果不合法,解释错误原因:
auto p = new int();
auto sp = make_shared<int>();
(a) process(sp);
(b) process(new int());
(c) process(p);
(d) process(shared_ptr<int>(p));
合法。将智能指针作为参数传递给 process;
不合法。内置指针不能隐式转换为智能指针;
不合法。同上;
合法。但调用结束会将 p 变为空悬指针。
练习 12.13
如果执行下面的代码,会发生什么?
auto sp = make_shared<int>();
auto p = sp.get();
delete p;
sp 变成空悬指针。
12.1.4 节练习
练习 12.14
编写你自己版本的用 shared_ptr 管理 connection 的函数。
void end_connection(connection *p) { disconnect(*p); }
void f(destination &d)
{
connection c = connect(&d);
shared_ptr<connection> p(&c, end_connection);
}
练习 12.15
重写第一题的程序,用 lambda (参见10.3.2节,第346页)代替 end_connection 函数。
void f(destination &d)
{
connection c = connect(&d);
shared_ptr<connection> p(&c, [](connection *p) { disconnect(*p); });
}
12.1.5 节练习
练习 12.16
如果你试图拷贝或赋值 unique_ptr,编译器并不总是能给出易于理解的错误信息。编写包含这种错误的程序,观察编译器如何诊断这种错误。
#include<iostream>
#include<memory>
using namespace std;
int main()
{
unique_ptr<int> p1(new int(12));
// unique_ptr<int> p2(p1);
// unique_ptr<int> p3 = p1;
unique_ptr<int> p4;
p4.reset(p1.release());
return 0;
}
unique_ptr<int> p3 = p1;
declared here
unique_ptr(const unique_ptr&) = delete;
练习 12.17
下面的 unique_ptr 声明中,哪些是合法的,哪些可能导致后续的程序错误?解释每个错误的问题在哪里。
int ix = 1024, *pi = &ix, *pi2 = new int(2048);
typedef unique_ptr<int> IntP;
(a) IntP p0(ix);
(b) IntP p1(pi);
(c) IntP p2(pi2);
(d) IntP p3(&ix);
(e) IntP p4(new int(2048));
(f) IntP p5(p2.get());
非法。应该绑定在动态指针上;
编译通过,后续错误。由于 pi 不是 new 出的动态指针,在离开作用域时,调用 delete 销毁报错;
编译通过,后续错误。绑定了 unique_ptr 后,当销毁智能指针时,会释放内存,则 pi2 会变成空悬指针;
编译通过,后续错误。同(b);
合法。
编译通过,后续错误。出现空悬指针和两次 delete。
练习 12.18
shared_ptr 为什么没有 release 成员?
release 将放弃指针的控制权,并返回指针,用来给 unique_ptr 转移控制权,而 shared_ptr 可以有多个指针同时指向一个对象,可以直接赋值。
12.1.6 节练习
练习 12.19
定义你自己版本的 StrBlobPtr,更新 StrBlob 类,加入恰当的 friend 声明以及 begin 和 end 成员。
#ifndef STRBLOB_H_
#define STRBLOB_H_
#include<initializer_list>
#include<vector>
#include<string>
#include<memory>
#include<stdexcept>
class StrBlobPtr;
class StrBlob
{
public:
friend class StrBlobPtr;
typedef std::vector<std::string>::size_type size_type;
StrBlob();
StrBlob(std::initializer_list<std::string> il);
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
void push_back(const std::string &t) { data->push_back(t); }
void pop_back();
std::string &front();
std::string &back();
const std::string& front() const;
const std::string& back() const;
StrBlobPtr begin();
StrBlobPtr end();
private:
std::shared_ptr<std::vector<std::string>> data;
void check(size_type i, const std::string &msg) const;
};
StrBlob::StrBlob():data(std::make_shared<std::vector<std::string>>()){}
StrBlob::StrBlob(std::initializer_list<std::string> il):data(std::make_shared<std::vector<std::string>>(il)){}
void StrBlob::check(size_type i, const std::string &msg) const
{
if (i>=data->size())
{
throw std::out_of_range(msg);
}
}
std::string& StrBlob::front()
{
check(0, "front on empty StrBlob");
return data->front();
}
std::string &StrBlob::back()
{
check(0, "back on empty StrBlob");
return data->back();
}
void StrBlob::pop_back()
{
check(0, "pop_back on empty StrBlob");
data->pop_back();
}
class StrBlobPtr
{
public:
StrBlobPtr() : curr(0){}
StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz){}
bool operator!=(const StrBlobPtr& p) { return p.curr != curr; }
std::string &deref() const;
StrBlobPtr &incr();
private:
std::shared_ptr<std::vector<std::string>> check(std::size_t, const std::string &) const;
std::weak_ptr<std::vector<std::string>> wptr;
std::size_t curr;
};
std::shared_ptr<std::vector<std::string>> StrBlobPtr::check(std::size_t i, const std::string &msg) const
{
auto ret = wptr.lock();
if (!ret)
{
throw std::runtime_error("unbound StrBlobPtr");
}
if (i>=ret->size())
{
throw std::out_of_range(msg);
}
return ret;
}
std::string &StrBlobPtr::deref() const
{
auto p = check(curr, "dereference past end");
return (*p)[curr];
}
StrBlobPtr &StrBlobPtr::incr()
{
check(curr, "increment past end if StrBlobPtr");
++curr;
return *this;
}
StrBlobPtr StrBlob::begin()
{
return StrBlobPtr(*this);
}
StrBlobPtr StrBlob::end()
{
auto ret = StrBlobPtr(*this, data->size());
return ret;
}
#endif
练习 12.20
编写程序,逐行读入一个输入文件,将内容存入一个 StrBlob 中,用一个 StrBlobPtr 打印出 StrBlob 中的每个元素。
#include<iostream>
#include<fstream>
#include"StrBlob.h"
using namespace std;
int main()
{
ifstream in("text.txt");
StrBlob s;
string str;
while (getline(in,str))
{
s.push_back(str);
}
for (auto beg = s.begin(); beg != s.end();beg.incr())
{
cout << beg.deref() << endl;
}
return 0;
}
练习 12.21
也可以这样编写 StrBlobPtr 的 deref 成员:
std::string& deref() const {
return (*check(curr, "dereference past end"))[curr];
}
你认为哪个版本更好?为什么?
原版好一些,更容易读懂。
练习 12.22
为了能让 StrBlobPtr 使用 const StrBlob,你觉得应该如何修改?定义一个名为 ConstStrBlobPtr 的类,使其能够指向 const StrBlob。
修改 ConstStrBlobPtr 的构造函数,接受参数 const StrBlob &a,同时将 begin 和 end 成员函数改为 const 成员函数。
#ifndef STRBLOB_H_
#define STRBLOB_H_
#include<initializer_list>
#include<vector>
#include<string>
#include<memory>
#include<stdexcept>
class ConstStrBlobPtr;
class StrBlob
{
public:
friend class ConstStrBlobPtr;
typedef std::vector<std::string>::size_type size_type;
StrBlob();
StrBlob(std::initializer_list<std::string> il);
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
void push_back(const std::string &t) { data->push_back(t); }
void pop_back();
std::string &front();
std::string &back();
const std::string& front() const;
const std::string& back() const;
ConstStrBlobPtr begin() const;
ConstStrBlobPtr end() const;
private:
std::shared_ptr<std::vector<std::string>> data;
void check(size_type i, const std::string &msg) const;
};
StrBlob::StrBlob():data(std::make_shared<std::vector<std::string>>()){}
StrBlob::StrBlob(std::initializer_list<std::string> il):data(std::make_shared<std::vector<std::string>>(il)){}
void StrBlob::check(size_type i, const std::string &msg) const
{
if (i>=data->size())
{
throw std::out_of_range(msg);
}
}
std::string& StrBlob::front()
{
check(0, "front on empty StrBlob");
return data->front();
}
std::string &StrBlob::back()
{
check(0, "back on empty StrBlob");
return data->back();
}
void StrBlob::pop_back()
{
check(0, "pop_back on empty StrBlob");
data->pop_back();
}
class ConstStrBlobPtr
{
public:
ConstStrBlobPtr() : curr(0){}
ConstStrBlobPtr(const StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz){}
bool operator!=(const ConstStrBlobPtr& p) { return p.curr != curr; }
std::string &deref() const;
ConstStrBlobPtr &incr();
private:
std::shared_ptr<std::vector<std::string>> check(std::size_t, const std::string &) const;
std::weak_ptr<std::vector<std::string>> wptr;
std::size_t curr;
};
std::shared_ptr<std::vector<std::string>> ConstStrBlobPtr::check(std::size_t i, const std::string &msg) const
{
auto ret = wptr.lock();
if (!ret)
{
throw std::runtime_error("unbound ConstStrBlobPtr");
}
if (i>=ret->size())
{
throw std::out_of_range(msg);
}
return ret;
}
std::string &ConstStrBlobPtr::deref() const
{
auto p = check(curr, "dereference past end");
return (*p)[curr];
}
ConstStrBlobPtr &ConstStrBlobPtr::incr()
{
check(curr, "increment past end if ConstStrBlobPtr");
++curr;
return *this;
}
ConstStrBlobPtr StrBlob::begin()
> 这里是引用
{
return ConstStrBlobPtr(*this);
}
ConstStrBlobPtr StrBlob::end()
{
auto ret = ConstStrBlobPtr(*this, data->size());
return ret;
}
#endif
12.2.1 节练习
练习 12.23
编写一个程序,连接两个字符串字面常量,将结果保存在一个动态分配的 char 数组中。重写这个程序,连接两个标准库 string 对象。
#include<iostream>
#include<new>
#include<string>
#include<cstring>
using namespace std;
int main()
{
char a[] = "aaa";
char b[] = "bbb";
char *p1 = new char[strlen(a)+strlen(b)+1];
strcpy(p1, a);
strcat(p1, b);
cout << p1 << endl;
delete[] p1;
string str1 = "aaa", str2 = "bbb";
string *p2 = new string;
*p2 = str1 + str2;
cout << *p2 << endl;
delete p2;
return 0;
}
练习 12.24
编写一个程序,从标准输入读取一个字符串,存入一个动态分配的字符数组中。描述你的程序如何处理变长输入。测试你的程序,输入一个超出你分配的数组长度的字符串。
#include<iostream>
#include<cstring>
using namespace std;
int main()
{
string str;
cin >> str;
char *p = new char[str.size()+1];
strcpy(p, str.c_str());
cout << p << endl;
delete[] p;
return 0;
}
练习 12.25
给定下面的 new 表达式,你应该如何释放 pa?
int *pa = new int[10];
delete[] pa;
12.2.2 节练习
练习 12.26
用 allocator 重写第 427 页中的程序。
#include<iostream>
#include<memory>
#include<string>
using namespace std;
int main()
{
allocator<string> alloc;
auto const p = alloc.allocate(6);
auto q=p;
string s;
while (cin>>s&&q!=p+6)
{
alloc.construct(q++,s);
}
while (q!=p)
{
alloc.destroy(--q);
}
alloc.deallocate(p, 6);
return 0;
}
12.3.1 节练习
练习 12.27
TextQuery 和 QueryResult 类只使用了我们已经介绍过的语言和标准库特性。不要提前看后续章节内容,只用已经学到的知识对这两个类编写你自己的版本。
TextQuery.h
#ifndef TEXTQUERY_H_
#define TEXTQUERY_H_
#include <fstream>
#include <iostream>
#include <map>
#include <memory>
#include <set>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
class QueryResult; // 为定义函数 query 的返回类型,这个定义是必须的
class TextQuery
{
public:
using line_no = vector<string>::size_type;
TextQuery(ifstream &);
QueryResult query(const string &) const;
private:
shared_ptr<vector<string>> file; // 输入文件
// 每个单词到它所在的行号的集合映射
map<string, shared_ptr<set<line_no>>> wm;
};
// 读取输入文件并建立单词到行号的映射
TextQuery::TextQuery(ifstream &is) : file(new vector<string>)
{
string text;
while (getline(is, text))
{ // 对文件中每一行
file->push_back(text); // 保存此行文本
int n = file->size() - 1; // 当前行号
istringstream line(text); // 将文本分解为单词
string word;
while (line >> word) // 对行中每个单词
{ // 如果单词不在 wm 中,以之为下标在 wm 中添加一项
auto &lines = wm[word]; // lines 是一个 shared_ptr
if (!lines) // 在我们第一次遇到这个单词时,此指针为空
{
lines.reset(new set<line_no>); // 分配一个新的 set
}
lines->insert(n); //将此行号插入 set 中
}
}
}
class QueryResult
{
friend ostream &print(ostream &, const QueryResult &);
public:
QueryResult(string s, shared_ptr<set<TextQuery::line_no>> p, shared_ptr<vector<string>> f) : sought(s), lines(p), file(f) {}
private:
string sought; // 查询单词
shared_ptr<set<TextQuery::line_no>> lines; // 出现的行号
shared_ptr<vector<string>> file; // 输入文件
};
QueryResult TextQuery::query(const string &sought) const
{
// 如果未找到 sought,我们将返回一个指向此 set 的指针
static shared_ptr<set<line_no>> nodata(new set<line_no>);
// 使用 find 而不是下标运算符来查找单词,避免将单词添加到 wm 中!
auto loc = wm.find(sought);
if (loc==wm.end())
{
return QueryResult(sought, nodata, file); // 未找到
}
else
{
return QueryResult(sought, loc->second, file);
}
}
ostream &print(ostream &os,const QueryResult &qr)
{
// 如果找到了单词,打印出现的次数和所以出现的位置
os << qr.sought << " occers " << qr.lines->size() << " " /* << make_plural(qr.lines->size(), "time", "s") */ << endl;
// 打印单词出现的每一行
for(auto num:*qr.lines) // 对 set 中每个单词
{
// 避免行号从 0 开始给用户带来的困惑
os << "\t(line" << num + 1 << ")" << *(qr.file->begin() + num) << endl;
}
return os;
}
#endif
练习 12.28
编写程序实现文本查询,不要定义类来管理数据。你的程序应该接受一个文件,并与用户交互来查询单词。使用 vector、map 和 set 容器来保存来自文件的数据并生成查询结果。
#include "TextQuery.h"
#include <iostream>
void runQueries(ifstream &f)
{
TextQuery tq(f);
while (true)
{
cout << "enter word to look for, or q to quit: ";
string s;
if (!(cin >> s) || s == "q")
break;
print(cout, tq.query(s)) << endl;
}
}
int main()
{
ifstream file("words.txt");
runQueries(file);
return 0;
}
练习 12.29
我们曾经用 do while 循环来编写管理用户交互的循环。用 do while 重写本节程序,解释你倾向于哪个版本,为什么?
#include "TextQuery.h"
#include <iostream>
void runQueries(ifstream &f)
{
TextQuery tq(f);
do
{
cout << "enter word to look for, or q to quit: ";
string s;
if (!(cin >> s) || s == "q")
break;
print(cout, tq.query(s)) << endl;
} while (true);
}
int main()
{
ifstream file("words.txt");
runQueries(file);
return 0;
}
12.3.2 节练习
练习 12.30
定义你自己版本的 TextQuery 和 QueryResult 类,并执行 12.3.1节中的 runQueries 函数。
同 12.27 12.28
练习 12.31
如果用 vector 代替 set 保存行号,会有什么差别?哪个方法更好?为什么?
set 可以保证元素唯一性,同一行单词多次出现只记录一次行号。
练习 12.32
重写 TextQuery 和 QueryResult类,用 StrBlob 代替 vector 保存输入文件。
#ifndef TEXTQUERY_H_
#define TEXTQUERY_H_
#include "StrBlob.h"
#include <fstream>
#include <map>
#include <memory>
#include <set>
#include <sstream>
#include <string>
#include <vector>
class QueryResult; // 为定义函数 query 的返回类型,这个定义是必须的
class TextQuery
{
public:
using line_no = std::vector<std::string>::size_type;
TextQuery(std::ifstream &);
QueryResult query(const std::string &) const;
private:
StrBlob file; // 输入文件
// 每个单词到它所在的行号的集合映射
std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;
};
// 读取输入文件并建立单词到行号的映射
TextQuery::TextQuery(std::ifstream &is)
{
std::string text;
while (getline(is, text))
{ // 对文件中每一行
file.push_back(text); // 保存此行文本
int n = file.size() - 1; // 当前行号
std::istringstream line(text); // 将文本分解为单词
std::string word;
while (line >> word) // 对行中每个单词
{ // 如果单词不在 wm 中,以之为下标在 wm 中添加一项
auto &lines = wm[word]; // lines 是一个 shared_ptr
if (!lines) // 在我们第一次遇到这个单词时,此指针为空
{
lines.reset(new std::set<line_no>); // 分配一个新的 set
}
lines->insert(n); //将此行号插入 set 中
}
}
}
class QueryResult
{
friend std::ostream &print(std::ostream &, const QueryResult &);
public:
QueryResult(std::string s, std::shared_ptr<std::set<TextQuery::line_no>> p, StrBlob f) : sought(s), lines(p), file(f) {}
private:
std::string sought; // 查询单词
std::shared_ptr<std::set<TextQuery::line_no>> lines; // 出现的行号
StrBlob file; // 输入文件
};
QueryResult TextQuery::query(const std::string &sought) const
{
// 如果未找到 sought,我们将返回一个指向此 set 的指针
static std::shared_ptr<std::set<line_no>> nodata(new std::set<line_no>);
// 使用 find 而不是下标运算符来查找单词,避免将单词添加到 wm 中!
auto loc = wm.find(sought);
if (loc == wm.end())
{
return QueryResult(sought, nodata, file); // 未找到
}
else
{
return QueryResult(sought, loc->second, file);
}
}
std::ostream &print(std::ostream &os, const QueryResult &qr)
{
// 如果找到了单词,打印出现的次数和所有出现的位置
os << qr.sought << " occers " << qr.lines->size() << " " /* << make_plural(qr.lines->size(), "time", "s") */ << std::endl;
// 打印单词出现的每一行
for (auto num : *qr.lines) // 对 set 中每个单词
{
ConstStrBlobPtr p(qr.file, num);
// 避免行号从 0 开始给用户带来的困惑
os << "\t(line " << num + 1 << ") " << p.deref() << std::endl;
}
return os;
}
#endif
12.32.cpp
#include "TextQuery2.h"
#include <iostream>
using namespace std;
void runQueries(ifstream &f)
{
TextQuery tq(f);
do
{
cout << "enter word to look for, or q to quit: ";
string s;
if (!(cin >> s) || s == "q")
break;
print(cout, tq.query(s)) << endl;
} while (true);
}
int main()
{
ifstream file("words.txt");
runQueries(file);
return 0;
}
练习 12.33
在第15章中我们将扩展查询系统,在 QueryResult 类中将会需要一些额外的成员。添加名为 begin 和 end 的成员,返回一个迭代器,指向一个给定查询返回的行号的 set 中的位置。再添加一个名为 get_file 的成员,返回一个 shared_ptr,指向 QueryResult 对象中的文件。
#ifndef TEXTQUERY_H_
#define TEXTQUERY_H_
#include "StrBlob.h"
#include <fstream>
#include <map>
#include <memory>
#include <set>
#include <sstream>
#include <string>
#include <vector>
class QueryResult; // 为定义函数 query 的返回类型,这个定义是必须的
class TextQuery
{
public:
using line_no = std::vector<std::string>::size_type;
TextQuery(std::ifstream &);
QueryResult query(const std::string &) const;
private:
StrBlob file; // 输入文件
// 每个单词到它所在的行号的集合映射
std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;
};
// 读取输入文件并建立单词到行号的映射
TextQuery::TextQuery(std::ifstream &is)
{
std::string text;
while (getline(is, text))
{ // 对文件中每一行
file.push_back(text); // 保存此行文本
int n = file.size() - 1; // 当前行号
std::istringstream line(text); // 将文本分解为单词
std::string word;
while (line >> word) // 对行中每个单词
{ // 如果单词不在 wm 中,以之为下标在 wm 中添加一项
auto &lines = wm[word]; // lines 是一个 shared_ptr
if (!lines) // 在我们第一次遇到这个单词时,此指针为空
{
lines.reset(new std::set<line_no>); // 分配一个新的 set
}
lines->insert(n); //将此行号插入 set 中
}
}
}
class QueryResult
{
friend std::ostream &print(std::ostream &, const QueryResult &);
public:
QueryResult(std::string s, std::shared_ptr<std::set<TextQuery::line_no>> p, StrBlob f) : sought(s), lines(p), file(f) {}
std::set<TextQuery::line_no>::iterator begin() const { return lines->begin(); }
std::set<TextQuery::line_no>::iterator end() const { return lines->end(); }
std::shared_ptr<StrBlob> get_file() const { return std::make_shared<StrBlob>(file); }
private:
std::string sought; // 查询单词
std::shared_ptr<std::set<TextQuery::line_no>> lines; // 出现的行号
StrBlob file; // 输入文件
};
QueryResult TextQuery::query(const std::string &sought) const
{
// 如果未找到 sought,我们将返回一个指向此 set 的指针
static std::shared_ptr<std::set<line_no>> nodata(new std::set<line_no>);
// 使用 find 而不是下标运算符来查找单词,避免将单词添加到 wm 中!
auto loc = wm.find(sought);
if (loc == wm.end())
{
return QueryResult(sought, nodata, file); // 未找到
}
else
{
return QueryResult(sought, loc->second, file);
}
}
std::ostream &print(std::ostream &os, const QueryResult &qr)
{
// 如果找到了单词,打印出现的次数和所有出现的位置
os << qr.sought << " occers " << qr.lines->size() << " " /* << make_plural(qr.lines->size(), "time", "s") */ << std::endl;
// 打印单词出现的每一行
for (auto num : *qr.lines) // 对 set 中每个单词
{
ConstStrBlobPtr p(qr.file, num);
// 避免行号从 0 开始给用户带来的困惑
os << "\t(line " << num + 1 << ") " << p.deref() << std::endl;
}
return os;
}
#endif