本文是本人大一期间在校学习C++课程时所撰写的实验报告的摘录,由于刚上大学,刚接触计算机编程方面的相关知识,故可能会有很多不足甚至错误的地方,还请各位看官指出及纠正。
本文所涉及到的“教材”指:
电子工业出版社《C++ Primary中文版(第5版)》
如需转载或引用请标明出处。
设计思路
文本查询程序
该程序要求用户在一个给定的文本中输入要查询的单词,查询的结果是该单词在文本中出现的次数及其所在的行的列表。根据这个要求,可以预见到该程序需要做到:
- 当程序读入文件时,要记住单词出现的每一行。因此,程序需要逐行读取输入的文件,并且将每一行分解为独立的单词;
- 输出结果时,要能提取每个单词所关联的行号,且行号必须按一定顺序出现且无重复,还要能够打印给定行号中的文本。
数据结构
使用之前学过的各种标准库类型可以简单且方便地实现上述要求:
- 使用一个存储string地vector用于保存输入文本地拷贝,其中的每一个元素是输入文本对应的每一行;
- 使用一个istringstream,将每一行切分为一个个的单词;
- 使用一个set来保存每个单词在文本中出现的行号,从而保证了输出的规范性;
- 使用一个map来讲每个单词与它出现行号的集合set关联起来,以方便地提取任意单词的行号集合。
上述的vector、set和map使用智能共享指针shared_ptr来进行相关操作。
此外,定义一个TextQuery类用于执行读入文本、查询等的相关操作,还定义一个QueryResult类用于表示查询的结果。这两个类有助于模块化代码,使整个解决方案更加有效。
代码细节
总的来说,整个程序的运行分成以下五步:
- 读取指定文本,按行划分,存入vector
- 划分每一行,形成从每个单词到其出现的位置的集合的映射
- 用户输入单词,执行查询操作
- 从已分好的映射中读取对应的集合
- 输出结果
其它地方还实现了规范化文本、新定义begin、end函数等操作,具体细节详见代码注释部分。
源代码
TextQuery.h
#ifndef TEXTQUERY_H
#define TEXTQUERY_H
#include <memory>
#include <string>
#include <vector>
#include <map> //使用map
#include <set> //使用set
#include <fstream>
#include "QueryResult.h"
class QueryResult; //因为TextQuery有成员函数返回QueryResult,故需事先声明
class TextQuery
{
public:
typedef std::vector<std::string>::size_type line_no; //line_no是的vector的size_type,用于指示行号
TextQuery(std::ifstream&); //TextQuery的构造函数
QueryResult query(const std::string&) const; //执行查询操作
void display_map(); //用于打印查找映射的文本的调试函数
private:
std::shared_ptr<std::vector<std::string>> file; //file指向存储文本每一行的vector
std::map<std::string, std::shared_ptr<std::set<line_no>>> wm; //将每个要查询的单词映射到存储其出现行号的集合
static std::string cleanup_str(const std::string&); //将文本中的标点符号删去,并小写所有字母
};
#endif
TextQuery.cpp
#include "TextQuery.h"
#include "make_plural.h" //使用复数形式输出函数
#include <cstddef>
#include <memory>
#include <sstream> //使用istringstream,将行切分为单词
#include <string>
#include <vector>
#include <map> //使用map
#include <set> //使用set
#include <iostream>
#include <fstream>
#include <cctype> //使用字符识别函数
#include <cstring>
#include <utility>
using namespace std;
//下面使用typedef简化代码
//wmType是将查询单词映射到出现行号的map
typedef map<string, shared_ptr<set<TextQuery::line_no>>> wmType;
//wmIter是wmType的常量迭代器
typedef wmType::const_iterator wmIter;
//lineType是指向存储行号的set的共享指针
typedef shared_ptr<set<TextQuery::line_no>> lineType;
//lineIter是存储行号的set的常量迭代器
typedef set<TextQuery::line_no>::const_iterator lineIter;
//构造函数:读取文本并建立起每一种单词映射到其出现的行号的集合map
TextQuery::TextQuery(ifstream &is): file(new vector<string>)
{
string text; //指示当前行
while (getline(is, text)) //对于文本中的每一行
{
file->push_back(text); //将该行存储到file中
int n = file->size() - 1; //n为当前这一行的行号
istringstream line(text); //string读入流line,用于将当前行划分为单词
string word; //指示当前单词
while (line >> word) //对于当前行的每一个单词
{
word = cleanup_str(word); //规范单词
//如果该单词是第一次出现,则会在wm新建一个该单词映射到行号的键值对
lineType &lines = wm[word]; //lines指向一个存储行号的set
if (!lines) //当第一次遇见单词时,lines指向NULL
lines.reset(new set<line_no>); //因此分配一个新的set用于存储该单词出现的行号
lines->insert(n); //将出现的行号放入到lines中
}
}
}
//移除标点符号并将字母小写化,以便查询可以无视大小写
string TextQuery::cleanup_str(const string &word)
{
string ret;
for (string::const_iterator it = word.begin(); it != word.end(); ++it)
{
if (!ispunct(*it)) //如果不是标点符号
ret += tolower(*it); //则小写化该字符
}
return ret; //返回处理后的单词
}
QueryResult TextQuery::query(const string &sought) const
{
static lineType nodata(new set<line_no>); //nodata是指向空行号集的lineType,在找不到单词时返回
wmIter loc = wm.find(cleanup_str(sought)); //loc接受map成员函数find的返回值
if (loc == wm.end()) //如果没找到
return QueryResult(sought, nodata, file); //则用nodata构造一个查询结果QueryResult作为返回值
else
return QueryResult(sought, loc->second, file); //否则返回用对应行号集合set构造的查询结果QueryResult
}
ostream &print(ostream &os, const QueryResult &qr)
{
//如果单词找到了,则输出出现次数和所有出现的位置
os << qr.sought << " occurs " << qr.lines->size() << " "
<< make_plural(qr.lines->size(), "time", "s") << endl;
//输出该单词出现的每一行
for (lineIter num = qr.lines->begin(); num != qr.lines->end(); ++num)
//编号从0开始,因此输出要加1
os << "\t(line " << *num + 1 << ") " << *(qr.file->begin() + *num) << endl;
return os;
}
//用于调试代码的函数
void TextQuery::display_map()
{
wmIter iter = wm.begin(), iter_end = wm.end();
//对于map中的每个单词
for ( ; iter != iter_end; ++iter) {
cout << "word: " << iter->first << " {";
//获取位置向量作为常量引用以避免复制它
lineType text_locs = iter->second;
lineIter loc_iter = text_locs->begin(),
loc_iter_end = text_locs->end();
//打印此单词的所有行号
while (loc_iter != loc_iter_end)
{
cout << *loc_iter;
if (++loc_iter != loc_iter_end)
cout << ", ";
}
cout << "}\n"; //结束输出
}
cout << endl;//结束打印整个map
}
QueryResult.h
#ifndef QUERYRESULT_H
#define QUERYRESULT_H
#include <memory>
#include <string>
#include <vector>
#include <set> //使用set
#include <iostream>
//QueryResult类表示查询结果
class QueryResult
{
friend std::ostream& print(std::ostream&, const QueryResult&); //声明友元函数print
public:
typedef std::vector<std::string>::size_type line_no; //line_no是的vector的size_type,用于指示行号
typedef std::set<line_no>::const_iterator line_it; //set存储行号,line_it是它的迭代器
QueryResult(std::string s, //构造查询结果类
std::shared_ptr<std::set<line_no> > p,
std::shared_ptr<std::vector<std::string> > f):
sought(s), lines(p), file(f) { }
std::set<line_no>::size_type size() const { return lines->size(); } //返回单词出现的次数
line_it begin() const { return lines->begin(); } //返回行号集合的开头迭代器
line_it end() const { return lines->end(); } //返回行号集合的尾后迭代器
std::shared_ptr<std::vector<std::string>> get_file() { return file; }
private:
std::string sought; //指示本次查询结果对应的单词
std::shared_ptr<std::set<line_no>> lines; //lines指向存储行号的集合set
std::shared_ptr<std::vector<std::string>> file; //file是指向存储文本的vector
};
std::ostream &print(std::ostream&, const QueryResult&); //打印查询结果
#endif
make_plural.h
#include <cstddef>
#include <string>
#include <iostream>
using namespace std;
#ifndef MAKE_PLURAL_H
#define MAKE_PLURAL_H
//输出时如果ctr大于1,则输出word对应的复数形式
inline string make_plural(size_t ctr, const string &word, const string &ending)
{
return (ctr > 1) ? word + ending : word;
}
#endif
querymain.cpp
#include <string>
#include <fstream>
#include <iostream>
using namespace std;
#include <cstdlib> //使用EXIT_FAILURE
#include "TextQuery.h"
#include "make_plural.h"
void runQueries(ifstream &infile)
{
//infile是我们想要查询的文本的文件输入流
TextQuery tq(infile);//存储文本,并构造其中每个单词到其出现位置集合的映射
//提示用户输入,并输出查询结果
while (true)
{
cout << "enter word to look for, or q to quit: ";
string s;
if (!(cin >> s) || s == "q") break; //当输入空行或“q”时关闭程序
print(cout, tq.query(s)) << endl; //输出查询结果
}
}
//该程序接受一个参数,此参数指定要查询的文件
int main(int argc, char **argv)
{
//infile是我们想要查询的文本的文件输入流
ifstream infile;
//由于open函数无返回值,因此我们使用逗号运算符去查询infile调用open后的状态
if (argc < 2 || !(infile.open(argv[1]), infile))
{ //如果文件打开失败,将错误输出到标准错误流
cerr << "No input file!" << endl;
return EXIT_FAILURE;
}
runQueries(infile); //打开成功,进行查询
system("pause");
return 0;
}