C++Primer学习笔记15.标准库
### 使用标准库文本查询程序要实现的功能:允许用户在一个给定文件中查询单词。查询结果是单词在文件中出现的次数及其所在行的列表(包括行号及对应文本),如果一个单词在一行中出现多次,此行只列出一次。
#### 文本查询程序设计
<b>需求分析</b>
要实现以上功能,此文本查询程序要做的工作:
1. 程序读取输入文件时,需要记住单词出现的每一行,并将每一行分解为独立的单词。
2. 程序生成输出时,需要能提取每个单词关联的行号,行号按升序出现且无重复,且能打印给定行号中的文本。
<b>数据结构设计</b>
使用以下成员存储所需数据:
1. 一个 vector:按行存储从文件中读取的文本,每一个 string 元素代表一行。
2. 使用 istringstraem 来将行文本分解为单词。
3. 一个 map>:存储文件中的每个单词及其对应的行号列表,键 string 为单词,键值 set 为对应的行号列表,set 保证每行只出现一次且行号按升序保存。
<b>类的设计</b>
定义两个类:一个类 TextQuery 负责保存从输入文件读取到的内容,提供 “读取输入文件” 和 “查询内容” 两个接口;
此外,因为我们要的返回结果包含多种内容(单词出现次数、所有关联行号及对应文本),为了一次性地将查询结果返回(而不是一个又一个的变量),还要定义一个类 QueryResult 用来保存所有查询结果。这个类有一个 print 函数,完成结果打印工作。
<b>在类之间共享数据</b>
进一步地设计:
1. QueryResult 要包含的内容包括单词出现次数、所有关联行号及对应文本,如果这些数据都拷贝得话会浪费时间,因此最好通过获取指针来避免拷贝操作。
2. 使用了指针后 QueryResult 就和 TextQuery 共享了数据,这时如果 TextQuery 先销毁了就会导致 QueryResult 引用不存在地数据。因此应该使用 shared_ptr 来共享数据。
<b>使用TextQuery类</b>
设计一个类时,在真正实现类的成员前先编写使用这个类的程序,是一种非常有用的方法。这可以看到类是否具有我们所需要的操作。
```cpp
void runQueries(ifstream& infile) {
TextQuery tq(infile);// 用指向文件的输入流初始化类,初始化时完成保存文件、建立查询 map 的工作。
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; // 查询并打印结果
}
}
```
#### 文本查询程序类的定义
TextQuery 的定义
```cpp
class TextQuery{
friend class QueryResult;
using line_no = std::vector<std::string>::size_type;
public:
TextQuery(std::ifstream &in_file);
~TextQuery() = default;
QueryResult query(const std::string &word) const;
private:
std::shared_ptr<std::vector<std::string>> text_lines_;
std::map<std::string, std::shared_ptr<std::set<line_no>>> word_indexs_;
};
```
TextQuery 构造函数
```cpp
TextQuery::TextQuery(std::ifstream &in_file) :
text_lines_(std::make_shared<std::vector<std::string>>()){
std::string line_temp;
while (std::getline(in_file, line_temp)){
text_lines_->push_back(line_temp);
int line_num = text_lines_->size() - 1; // 当前行号
std::istringstream in_line(line_temp);
std::string word;
// 读取空格分隔的单词直接用 >> 运算符即可
while (in_line >> word) {
auto &lines = word_indexs_; // lines 是一个 shared_ptr 的引用
if (!lines) // 如果 word_indexs_ 中不存在 word,那么 lines 会是一个空指针
lines.reset(new std::set<line_no>()); // 分配一个新的 set
lines->insert(line_num); // 插入当前行号
}
}
}
```
QueryResult 的定义
```cpp
class QueryResult{
friend std::ostream &print(std::ostream &os, const QueryResult &qr);
using line_no = std::vector<std::string>::size_type;
public:
QueryResult(std::string word,
std::shared_ptr<std::set<line_no>> word_index,
std::shared_ptr<std::vector<std::string>> text_lines) : word_(word), word_index_(word_index), text_lines_(text_lines) {}
private:
std::string word_;
std::shared_ptr<std::set<line_no>> word_index_;
std::shared_ptr<std::vector<std::string>> text_lines_;
};
```
query 函数
直接返回 QueryResult 即可,不必将其作为参数传入其中。
```cpp
// 注意:函数定义时也要加 const
QueryResult TextQuery::query(const std::string &word) const {
if (word_indexs_.find(word) == word_indexs_.end())
return QueryResult(word, std::make_shared<std::set<line_no>>(), text_lines_);
else
return QueryResult(word, word_indexs_.at(word), text_lines_);
}
```
打印结果
print 函数的定义遵循了标准库中 print 函数的惯例,第一个参数和返回值都是流。
```cpp
std::ostream &print(std::ostream &os, const QueryResult &qr){
os << qr.word_ << " occurs " << qr.word_index_->size() << " times " << std::endl;
for (auto num : *qr.word_index_)
os << "\t(line " << num + 1 << ") " << *(qr.text_lines_->begin() + num) << std::endl;
return os;
}
```
页:
[1]