Chapter12&Chapter13:程序实例

文本查询程序

要求:程序允许用户在一个给定文件中查询单词。查询结果是单词在文件中出现的次数及所在行的列表。如果一个单词在一行中出现多次,此行只列出一次。

对要求的分析:

1.读入文件,必须记住单词出现在每一行。因此,程序需要逐行读取文件,并将每一行分解成独立的单词;

2. 程序生成输出时,它必须能提取每个单词所关联的行号;行号必须按升序出现且无重复;必须能打印给定行号的文本。

数据结构分析:

使用vector<string>来保存整个文件的拷贝;使用set来保存每个单词出现的行号;使用map将单词与行号关联起来。

我们在此基础上,再抽象一层:

定义一个保存文本输入的类,叫TextQuery。有两个操作:一是读取文件构造对象;二是执行查询操作。

查询操作:返回单词所在行及其文本。此结果定义一个类:QueryResult。

TextQuery与QueryResult的关系

由于QueryResult所需要的数据都保存在一个QTextQuery对象中,我们必须确定如何访问它们。我们可以拷贝行号的set,但这样很耗时。而且我们不希望拷贝vector。

所以两个类共享了数据

 1 class QueryResult;
 2 
 3 class TextQuery
 4 {
 5 public:
 6     using line_no = vector<string>::size_type;
 7     TextQuery(ifstream&);
 8     QueryResult query(const string&) const;//不完全类型可以声明(但不能定义)为参数类型和返回类型
 9 private:
10     shared_ptr<vector<string>> file;
11     map<string, shared_ptr<set<line_no>>> wm;
12 };
13 
14 TextQuery::TextQuery(ifstream& is)
15     :file(new vector<string>)
16 {
17     string text;
18     while (getline(is, text))
19     {
20         file->push_back(text);
21         int n = file->size() - 1;
22         istringstream line(text);
23         string word;
24         while (line >> word)
25         {
26             auto &lines = wm[word];
27             if (!lines)
28                 lines.reset(new set<line_no>);
29             lines->insert(n);
30         }
31     }
32 }
33 
34 class QueryResult
35 {
36     friend ostream& print(ostream&, const QueryResult&);
37 public:
38     using line_no = vector<string>::size_type;
39     QueryResult(string s, shared_ptr<set<line_no>> p, shared_ptr<vector<string>> f)
40         :sought(s), lines(p), file(f) {}
41 private:
42     string sought;
43     shared_ptr<set<line_no>> lines;
44     shared_ptr<vector<string>> file;
45 };
46 
47 //如果没有找到string,应该返回什么?
48 //我们定义一个局部static对象,它指向一个空行号set的shared_ptr,未找到单词,则返回此对象的一个拷贝
49 QueryResult TextQuery::query(const string &sought) const
50 {
51     static shared_ptr<set<line_no>> nodata(new set<line_no>);
52     //不使用下标运算符来查找,避免将单词添加到wm中
53     auto loc = wm.find(sought);
54     if (loc == wm.end())
55         return { sought, nodata, file };
56     else
57         return { sought, loc->second, file };
58 }
59 
60 ostream& print(ostream &os, const QueryResult &qr)
61 {
62     os << qr.sought << " occurs " << qr.lines->size() << " time(s)" << endl;
63     for (auto num : *qr.lines)
64         os << "	(line " << num + 1 << ") " << *(qr.file->begin() + num) << endl;
65     return os;
66 }
67 
68 void runQueries(ifstream& infile)
69 {
70     TextQuery tq(infile);
71     while (true)
72     {
73         cout << "enter word to look for, or q to quit: ";
74         string s;
75         if (!(cin >> s) || s == "q")
76             break;
77         print(cout, tq.query(s)) << endl;
78     }
79 }

邮件与目录

资源管理并不是类需要定义自己的拷贝控制成员的唯一原因。一些类也需要拷贝控制成员的帮助来进行簿记工作或其他操作。

要求:有两个类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工具函数来完成这些工作。

  1 class Message
  2 {
  3     friend class Folder;
  4     friend void swap(Message&, Message&);
  5 public:
  6     explicit Message(const string &str = "")
  7         :contents(str) {}
  8     Message(const Message&);
  9     Message& operator=(const Message&);
 10     Message(Message &&m);
 11     Message& Message::operator=(Message &&rhs);
 12     ~Message();
 13     void save(Folder&);
 14     void remove(Folder&);
 15 private:
 16     string contents;
 17     set<Folder*> folders;
 18     void addToFolders(const Message&);
 19     void removeFromFolders();
 20     void moveFolders(Message *m);
 21 };
 22 
 23 void Message::save(Folder &f)
 24 {
 25     folders.insert(&f);
 26     f.addMsg(this);
 27 }
 28 
 29 void Message::remove(Folder &f)
 30 {
 31     folders.erase(&f);
 32     f.remMsg(this);
 33 }
 34 //拷贝控制:
 35 void Message::addToFolders(const Message &m)
 36 {
 37     for (auto f : m.folders)
 38         f->addMsg(this);
 39 }
 40 void Message::removeFromFolders()
 41 {
 42     for (auto f : folders)
 43         f->remMsg(this);
 44 }
 45 
 46 Message::Message(const Message &m)
 47     :contents(m.contents),folders(m.folders)
 48 {
 49     addToFolders(m);
 50 }
 51 Message::~Message()
 52 {
 53     removeFromFolders();
 54 }
 55 Message& Message::operator=(const Message &rhs)
 56 {
 57     //先从左侧对象的folders中删除此Message指针,然后再添加到右侧运算对象的folders中,从而实现自赋值的正确处理
 58     removeFromFolders();
 59     contents = rhs.contents;
 60     folders = rhs.folders;
 61     addToFolders(rhs);
 62     return *this;
 63 }
 64 //定义自己的swap版本
 65 void swap(Message &lhs, Message &rhs)
 66 {
 67     using std::swap;
 68     for (auto f : lhs.folders)
 69         f->remMsg(&lhs);
 70     for (auto f : rhs.folders)
 71         f->remMsg(&rhs);
 72 
 73     swap(lhs.folders, rhs.folders);
 74     swap(lhs.contents, rhs.contents);
 75     for (auto f : lhs.folders)
 76         f->addMsg(&lhs);
 77     for (auto f : rhs.folders)
 78         f->addMsg(&rhs);
 79 }
 80 
 81 //移动操作
 82 void Message::moveFolders(Message *m)
 83 {
 84     folders = std::move(m->folders);//使用set的移动赋值运算符
 85     for (auto f : folders)
 86     {
 87         f->remMsg(m);
 88         f->addMsg(this);
 89     }
 90     m->folders.clear();
 91 }
 92 
 93 Message::Message(Message &&m)
 94     :contents(std::move(m.contents))
 95 {
 96     moveFolders(&m);
 97 }
 98 
 99 Message& Message::operator=(Message &&rhs)
100 {
101     if (this != &rhs)
102     {
103         removeFromFolders();
104         contents = std::move(rhs.contents);
105         moveFolders(&rhs);
106     }
107     return *this;
108 }

上述代码以Message类为例。

原文地址:https://www.cnblogs.com/wangyanphp/p/5838908.html