首页 > 代码库 > 第十四篇:一个文本查询程序的实现
第十四篇:一个文本查询程序的实现
前言
本文将讲解一个经典的文本查询程序,对前面所学的容器相关知识进行一个从理论到实际的升华,同时也对即将学习的面向对象知识来一次初体验。
程序描述
要求实现这样一个程序:读取用户指定的文件,然后允许用户从中查找某个单词所在的位置。
一个面向过程的落后的设计思想
将待检索文件以行为单位存放到Vector容器中,然后遍历容器,将容器内元素依次转存到字符串流对象中,然后在内层遍历这个字符串流对象,检索是否存在与给定单词匹配的单词。如果有则输出该行内容以及该行序号。
落后的原因及先进的设计思想
这是我以前尝试解决这个问题的思路。其本质是一个典型的面向过程思想,只不过用容器简化了些操作罢了。
这种思路使得每次执行检索都要重新遍历一次文件。如果当文件比较大的时候,这样的检索效率是不能为用户所接受的。
最好的方法应该是采用面向对象的方法,设定一个类,该类封装一个数据结构专门记载关于单词与行号的信息,该类还同时封装初始化函数,查询函数等功能函数。
另外,如果我这里不设定一个类,而是直接全局定义一个数据结构来记载关于单词与行号的信息,那么当还要对文本实现一些其他功能的时候,程序将会变得杂乱无章,代码里到处都是乱七八糟的数据结构和全局变量。这就是类封装性的好处,也是面向对象的美妙之处之一。
下面,将用面向对象的思想“美妙”地设计出这个文本查询程序... ...
第一步:设计类
第1步:确定类所包含的方法
1. read_file 函数:将指定的待检索文件存入容器并初始化”单词-行号“数据结构
2. run_query 函数:获取待查询单词并返回单词在文本中的行号
3. text_line 函数:获取某个行号,返回文件中该行的内容。
第2步:确定类所包含的数据
1. 一个string对象存放要查询的单词
2. 一个vector容器存放待检索的文本
3. 一个map容器存放单词和它对应的行号
类定义如下():
1 class TextQuery { 2 public: 3 // 为行号类型取个别名( 行号的类型实在是太长了 ) 4 typedef std::vector<std::string>::size_type line_no; 5 // 将数据存入vector容器并初始化单词 - 行号数据结构 6 void read_file(std::ifstream &is) { 7 // 将待检索文件存入容器 8 store_file(is); 9 // 建立单词 - 行号数据结构10 build_map();11 }12 // 根据用户指定的单词执行查询并返回结果行号( 结果是放在一个set容器中的 )。13 std::set<line_no> run_query(const std::string&) const;14 // 根据行号返回该行内容15 std::string text_line(line_no) const;16 private:17 // 下面这两个函数是上面read_file 函数的实现函数,是内部函数因此设为私有。18 void store_file(std::ifstream&); // 将数据存入vector容器19 void build_map(); // 初始化单词 - 行号数据结构20 // 一个vector容器21 std::vector<std::string> lines_of_text;22 // 一个单词 - 行号数据结构23 std::map< std::string, std::set<line_no> > word_map; // 注意这个是容器的容器 因此尖括号后面要留空格24 };
如此一来,整个程序的框架就显得豁然开朗。可见设计类这一环节的重要性。事实上,工程中常用UML之类的技术专门处理这个环节。
第二步:实现类
1. 实现store_file 函数:
1 void TextQuery::store_file(ifstream & is) {2 string textline;3 while (getline(is, textline))4 lines_of_text.push_back(textline);5 }
2. 实现bulid_map 函数:
1 void TextQuery::build_map()2 {3 for (line_no line_num = 0; line_num != lines_of_text.size()) {4 istringstream line(lines_of_text[line_num]);5 string word;6 while (line >> word)7 word_map[word].insert(line_num);8 }9 }
3. 实现run_query 函数:
1 set<TextQuery::line_no>2 TextQuery::run_query(const string &query_word) const {3 map< string, set<line_no> >::const_iterator loc = word_map.find(query_word);4 if (loc == word_map.end())5 return set<line_no>();6 else7 return loc->second;8 }
4. 实现text_line 函数:
1 string TextQuery::text_line(line_no line) const {2 if (line < lines_of_text.size()) {3 return lines_of_text(line);4 }5 throw std::out_of_range("line number out of range");6 }
第三步:编写主函数( 其实可以和第二步同时进行 提高效率 )
1 void print_results(const set<TextQuery::line_no> & locs, const string &sought, const TextQuery &file); 2 ifstream & open_file(ifstream &in, const string &file); 3 string make_plural(size_t ctr, const string &word, const string &ending); 4 5 int main(int argc, char **argv) 6 { 7 ifstream infile; 8 if (argc < 2 || !open_file(infile, argv[1])) { 9 cerr << "No input file!" << endl;10 return EXIT_FAILURE;11 }12 13 TextQuery tq;14 tq.read_file(infile);15 16 while (true) {17 cout << "inter a word to query:" << endl;18 string s;19 cin >> s;20 if (!cin || s == "q") break;21 set<TextQuery::line_no> locs = tq.run_query(s);22 print_results(locs, s, tq);23 }24 25 return 0;26 }27 28 void print_results(const set<TextQuery::line_no> & locs, const string &sought, const TextQuery &file) {29 // 为了表示下面这个size_type类型,还真煞费苦心。30 typedef set<TextQuery::line_no> line_nums;31 line_nums::size_type size = locs.size();32 cout << endl << sought << " occurs " << size << " " << make_plural(size, "time", "s") << endl;33 34 line_nums::const_iterator it = locs.begin();35 for (; it != locs.end(); ++it) {36 cout << "\t(line " << (*it) + 1 << ")" << file.text_line(*it) << endl;37 }38 }39 40 ifstream & open_file(ifstream &in, const string &file)41 {42 in.close();43 in.clear();44 45 in.open(file.c_str());46 47 return in;48 }49 50 string make_plural(size_t ctr, const string &word, const string &ending)51 {52 return (ctr == 1) ? word : word+ending;53 54 }
说明
第一步,第二步,第三步的代码文件分别为主函数源代码文件( .cpp ),类定义头文件( .h )和类实现源代码文件( .cpp )。
在构建这种工程时,头文件和命名空间的设定是有讲究的( 若要生成真正的可执行程序,上面的代码还要根据一些原则进行改动 ),本文不进行详述。
小结
1. 体验面向对象”工程“ ( 这一部分目前也有点还没弄清楚 待深入学习 )
2. 灵活使用容器
3. 感受面向对象思想
第十四篇:一个文本查询程序的实现