文本查询程序
要求:程序允许用户在一个给定文件中查询单词。查询结果是单词在文件中出现的次数及所在行的列表。如果一个单词在一行中出现多次,此行只列出一次。
对要求的分析:
1.读入文件,必须记住单词出现在每一行。因此,程序需要逐行读取文件,并将每一行分解成独立的单词;
2. 程序生成输出时,它必须能提取每个单词所关联的行号;行号必须按升序出现且无重复;必须能打印给定行号的文本。
数据结构分析:
使用vector<string>来保存整个文件的拷贝;使用set来保存每个单词出现的行号;使用map将单词与行号关联起来。
我们在此基础上,再抽象一层:
定义一个保存文本输入的类,叫TextQuery。有两个操作:一是读取文件构造对象;二是执行查询操作。
查询操作:返回单词所在行及其文本。此结果定义一个类:QueryResult。
TextQuery与QueryResult的关系
由于QueryResult所需要的数据都保存在一个QTextQuery对象中,我们必须确定如何访问它们。我们可以拷贝行号的set,但这样很耗时。而且我们不希望拷贝vector。
所以两个类共享了数据。
class QueryResult; 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() - ;
istringstream line(text);
string word;
while (line >> word)
{
auto &lines = wm[word];
if (!lines)
lines.reset(new set<line_no>);
lines->insert(n);
}
}
} class QueryResult
{
friend ostream& print(ostream&, const QueryResult&);
public:
using line_no = vector<string>::size_type;
QueryResult(string s, shared_ptr<set<line_no>> p, shared_ptr<vector<string>> f)
:sought(s), lines(p), file(f) {}
private:
string sought;
shared_ptr<set<line_no>> lines;
shared_ptr<vector<string>> file;
}; //如果没有找到string,应该返回什么?
//我们定义一个局部static对象,它指向一个空行号set的shared_ptr,未找到单词,则返回此对象的一个拷贝
QueryResult TextQuery::query(const string &sought) const
{
static shared_ptr<set<line_no>> nodata(new set<line_no>);
//不使用下标运算符来查找,避免将单词添加到wm中
auto loc = wm.find(sought);
if (loc == wm.end())
return { sought, nodata, file };
else
return { sought, loc->second, file };
} ostream& print(ostream &os, const QueryResult &qr)
{
os << qr.sought << " occurs " << qr.lines->size() << " time(s)" << endl;
for (auto num : *qr.lines)
os << "\t(line " << num + << ") " << *(qr.file->begin() + num) << endl;
return os;
} void runQueries(ifstream& infile)
{
TextQuery tq(infile);
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;
}
}
邮件与目录
资源管理并不是类需要定义自己的拷贝控制成员的唯一原因。一些类也需要拷贝控制成员的帮助来进行簿记工作或其他操作。
要求:有两个类Message和Folder,分别表示电子邮件消息和目录。每个Message可以出现在多个Folder中,Message内容只有一个副本——>如果一条Message的内容被改变,则我们从它所在的任何Folder来浏览此Message。
设计思路如下:
Message保存一个它所在Folder的指针的set;Folder保存一个它包含Message的指针的set。
当我们拷贝一个Message时,副本和原对象是不同的Message对象。因此,拷贝Message的操作包括消息内容和Folder指针set的拷贝;而且,我们必须在每个包含此消息的Folder中都添加一个指向新创建的Message的指针;
当我们销毁一个Message时,它将不复存在。因此,我们必须从包含此消息的所有Folder中删除指向此Message的指针;
当我们将一个Message对象赋予另一个Message对象时,左侧Message的内容会被右侧Message的内容所替代。同时,还必须更新Folder集合。
我们可以看到:
析构函数和拷贝赋值运算符都必须从包含一条Message的所有Folder中删除它。
拷贝构造函数和拷贝赋值运算符都要将一个Message添加到给定的一组Folder中。
我们定义两个private工具函数来完成这些工作。
class Message
{
friend class Folder;
friend void swap(Message&, Message&);
public:
explicit Message(const string &str = "")
:contents(str) {}
Message(const Message&);
Message& operator=(const Message&);
Message(Message &&m);
Message& Message::operator=(Message &&rhs);
~Message();
void save(Folder&);
void remove(Folder&);
private:
string contents;
set<Folder*> folders;
void addToFolders(const Message&);
void removeFromFolders();
void moveFolders(Message *m);
}; void Message::save(Folder &f)
{
folders.insert(&f);
f.addMsg(this);
} void Message::remove(Folder &f)
{
folders.erase(&f);
f.remMsg(this);
}
//拷贝控制:
void Message::addToFolders(const Message &m)
{
for (auto f : m.folders)
f->addMsg(this);
}
void Message::removeFromFolders()
{
for (auto f : folders)
f->remMsg(this);
} Message::Message(const Message &m)
:contents(m.contents),folders(m.folders)
{
addToFolders(m);
}
Message::~Message()
{
removeFromFolders();
}
Message& Message::operator=(const Message &rhs)
{
//先从左侧对象的folders中删除此Message指针,然后再添加到右侧运算对象的folders中,从而实现自赋值的正确处理
removeFromFolders();
contents = rhs.contents;
folders = rhs.folders;
addToFolders(rhs);
return *this;
}
//定义自己的swap版本
void swap(Message &lhs, Message &rhs)
{
using std::swap;
for (auto f : lhs.folders)
f->remMsg(&lhs);
for (auto f : rhs.folders)
f->remMsg(&rhs); swap(lhs.folders, rhs.folders);
swap(lhs.contents, rhs.contents);
for (auto f : lhs.folders)
f->addMsg(&lhs);
for (auto f : rhs.folders)
f->addMsg(&rhs);
} //移动操作
void Message::moveFolders(Message *m)
{
folders = std::move(m->folders);//使用set的移动赋值运算符
for (auto f : folders)
{
f->remMsg(m);
f->addMsg(this);
}
m->folders.clear();
} Message::Message(Message &&m)
:contents(std::move(m.contents))
{
moveFolders(&m);
} Message& Message::operator=(Message &&rhs)
{
if (this != &rhs)
{
removeFromFolders();
contents = std::move(rhs.contents);
moveFolders(&rhs);
}
return *this;
}
上述代码以Message类为例。