文本查询程序
要求:程序允许用户在一个给定文件中查询单词。查询结果是单词在文件中出现的次数及所在行的列表。如果一个单词在一行中出现多次,此行只列出一次。
对要求的分析:
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类为例。