文本查询项目--面向对象编程
抛弃c语言面向过程的写法,使用C++面向对象的思路,充分发挥封装继承多态。
Query_base是抽象基类,提供接口函数用,声明纯虚函数。
Query为一个查询类,它的作用的隐藏Query_base,它有一个只能指针指向Query_base,Query与Query_base是has_a的关系(继承则是is_a的关系)。
class Query { //重载逻辑运算符,生成最终的查询语句 friend Query operator~(const Query& rhs); friend Query operator|(const Query& lhs, const Query& rhs); friend Query operator&(const Query& lhs, const Query& rhs); public: Query(const string& word); //生成WordQuery类型的对象,赋值给_query QueryResult eval(const TextQuery& tq) const;//调用接口 string rep() const;//调用接口 private: Query(shared_ptr<Query_base> query);//构造函数,当要查询的就是一个单词时 /* ~Query(); */ shared_ptr<Query_base> _query; //用来动态决定调用那个版本的函数 与Query_base是has_a的关系 };
TextQuery与QueryResult都属于数据结构类,用于存放数据。
TextQuery用于存放文档,通过函数查询指定单词来返回一个QueryResult结果。
这两个类之间数据成员需要共享,所以使用shared_ptr智能指针。
TextQuery有两个数据成员,_lines用于保存文本的每一行的内容,map用于存放单词极其对应的行号,因为行号可以是多个,所以用容器set保存,可以起到自动排序的作用。
在这里为什么要using line_no = vector<string>::size_type?因为容器的元素个数可以大于int型的表示范围,所以使用size_type,而size_type在系统函数中是被size_t typedef定义的。
QueryResult作为Query的返回结果,需注意结果可能会是空值。
class TextQuery { public: TextQuery(ifstream& ifs);//构造函数,打开文件,获取文件的内容 QueryResult query(const string& word) const;//查询word,并返回查询结果 private: //由于QueryResult对象需要用到TextQuery对象中的数据成员 //避免在使用之前被销毁,将需要共享出去的成员定义为shared_ptr类型 shared_ptr<vector<string>> _lines; //保存文本每一行的内容 map<string, shared_ptr<set<line_no>>> _word2Nums;//保存单词出现的行号,从0开始 //一个单词可以出现在多行,所以行号用set容器保存 };
class QueryResult { //用来打印搜索结果 friend ostream& print(ostream& os, const QueryResult& qr); public: //构造函数 QueryResult(const string& word,shared_ptr<set<line_no>> lineNums,shared_ptr<vector<string>> lines); shared_ptr<vector<string>> get_lines();//返回所有按行存储的文本 line_no linesSize(); //返回搜索结果的总行数 auto begin();//返回_lineNums.begin() auto end(); //返回_lineNums.end() private: string _word;//要查询的单词 shared_ptr<set<line_no>> _lineNums; //单词出现的行号 shared_ptr<vector<string>> _lines; //文件的文本内容,按行保存 };
//text_query.h
#ifndef __TEXT_QUERY_H__ #define __TEXT_QUERY_H__ #include <vector> #include <map> #include <set> #include <memory> #include <string> #include <fstream> #include <ostream> using std::vector; using std::map; using std::set; using std::shared_ptr; using std::string; using std::ifstream; using std::ostream; using line_no = vector<string>::size_type;//定义一个类型成员来引用行号 //===============基础查询方法=================== class QueryResult;//为了定义函数query的返回类型,需要先声明 class TextQuery { public: TextQuery(ifstream& ifs);//构造函数,打开文件,获取文件的内容 QueryResult query(const string& word) const;//查询word,并返回查询结果 private: //由于QueryResult对象需要用到TextQuery对象中的数据成员 //避免在使用之前被销毁,将需要共享出去的成员定义为shared_ptr类型 shared_ptr<vector<string>> _lines; //保存文本每一行的内容 map<string, shared_ptr<set<line_no>>> _word2Nums;//保存单词出现的行号,从0开始 //一个单词可以出现在多行,所以行号用set容器保存 }; class QueryResult { //用来打印搜索结果 friend ostream& print(ostream& os, const QueryResult& qr); public: //构造函数 QueryResult(const string& word,shared_ptr<set<line_no>> lineNums,shared_ptr<vector<string>> lines); shared_ptr<vector<string>> get_lines();//返回所有按行存储的文本 line_no linesSize(); //返回搜索结果的总行数 auto begin();//返回_lineNums.begin() auto end(); //返回_lineNums.end() private: string _word;//要查询的单词 shared_ptr<set<line_no>> _lineNums; //单词出现的行号 shared_ptr<vector<string>> _lines; //文件的文本内容,按行保存 }; //================更加丰富的查询方法================= class Query; //声明 //公共抽象基类Query_base class Query_base { friend class Query; protected: Query_base();//构造函数 virtual ~Query_base() = default; //基类中有虚函数,需要虚析构函数,显式设置为默认函数体 /* virtual ~Query_base();//虚析构函数 */ private: //虚函数eval,使用给定的TextQuery对象查找与之匹配的行,并返回结果 virtual QueryResult eval(const TextQuery& tq) const = 0; //虚函数rep,返回实际要查询的单词 virtual string rep() const = 0; }; //供用户使用的接口类,隐藏了Query_base的继承体系 class Query { //重载逻辑运算符,生成最终的查询语句 friend Query operator~(const Query& rhs); friend Query operator|(const Query& lhs, const Query& rhs); friend Query operator&(const Query& lhs, const Query& rhs); public: Query(const string& word); //生成WordQuery类型的对象,赋值给_query QueryResult eval(const TextQuery& tq) const;//调用接口 string rep() const;//调用接口 private: Query(shared_ptr<Query_base> query);//构造函数,当要查询的就是一个单词时 /* ~Query(); */ shared_ptr<Query_base> _query; //用来动态决定调用那个版本的函数 与Query_base是has_a的关系 }; //查找给定的queryWord,实际在TextQuery对象上进行查询操作的唯一一个类 class WordQuery :public Query_base { friend class Query;//Query的构造函数会调用该类的构造函数,需要设为友元类 private: WordQuery(const string& queryWord);//构造函数 /* ~WordQuery();//析构函数 */ private: //覆盖eval和rep QueryResult eval(const TextQuery& tq) const override; string rep() const override; string _queryWord;//实际要查询的单词 }; //查询不包含某单词的类NotQuery class NotQuery :public Query_base { friend Query operator~(const Query& rhs); private: NotQuery(const Query& q);//构造函数 /* ~NotQuery();//重写虚析构函数 */ private: //覆盖eval和rep QueryResult eval(const TextQuery& tq) const override; string rep() const override; Query _query; }; ostream& operator<<(ostream& os, const Query& query); //==============与或查询派生类============= //与或查询的抽象基类,保存运算符左右两个运算对象 class BinaryQuery :public Query_base { protected: BinaryQuery(const Query& lhs, const Query& rhs, const string& op); //这个类不用覆盖eval string rep() const override; protected: //派生类需要使用下面的数据成员,所以设置为protected Query _lhs; Query _rhs; string _opSym;//保存运算符 "|"、"&" }; //与运算 class AndQuery :public BinaryQuery { friend Query operator&(const Query& lhs, const Query& rhs); private: AndQuery(const Query& lhs, const Query& rhs);//构造函数 //覆盖eval,rep直接使用从BinaryQuery基类继承过来的即可 QueryResult eval(const TextQuery& tq) const override; }; //或运算 class OrQuery :public BinaryQuery { friend Query operator|(const Query& lhs, const Query& rhs); private: OrQuery(const Query& lhs, const Query& rhs);//构造函数 //覆盖eval,rep直接使用从基类BinaryQuery继承过来的即可 QueryResult eval(const TextQuery& tq) const override; }; #endif
//text_query.cc
#include "text_query.h" #include <iostream> #include <sstream> #include <algorithm> // set_intersection #include <iterator> // insert_iterator using namespace std; //TextQuery的构造函数 TextQuery::TextQuery(ifstream& ifs) :_lines(new vector<string>) { //打开文件,读取文件内容,并保存在数据成员中 string line; size_t no = 0; string word; while (getline(ifs, line)) { //按行提取文本数据,并拆分成单词 _lines->push_back(line);//保存新的一行 //拆分单词 istringstream iss(line); while (iss >> word) { auto& wns = _word2Nums[word];//shared_ptr类型的引用 if (!wns) { //如果是新单词,那么指针就是空指针,需要新建 wns.reset(new set<line_no>); } wns->insert(no);//插入新行号,根据set的特性会去除重复的行号 } ++no;//行号加一 } } QueryResult TextQuery::query(const string& word) const { //返回QueryResult类型的对象,实际在这里进行对象的创建 //由于有可能要查找的单词不在文本中,所以需要定义一个空的set static shared_ptr<set<line_no>> nodata(new set<line_no>); auto loc = _word2Nums.find(word); if (loc == _word2Nums.end()) { //没有找到,传入一个空set //由于类型都是shared_ptr,所以传递过程中属于浅拷贝 return QueryResult(word, nodata, _lines); //shared_ptr<vector<string>> _lines; //保存文本每一行的内容 } else { //找到了,传入行号的set,和文本内容 return QueryResult(word, loc->second, _lines); //loc->second是set容器 } } QueryResult::QueryResult(const string& word, shared_ptr<set<line_no>> wns, shared_ptr<vector<string>> lines) :_word(word) , _lineNums(wns) , _lines(lines) {} //返回所有按行存储的_lines shared_ptr<vector<string>> QueryResult::get_lines() { return _lines; } //返回搜索结果的总行数 line_no QueryResult::linesSize() { return _lines->size(); } //返回_lineNums.begin() auto QueryResult::begin() { return _lineNums->begin(); } //返回_lineNums.end() auto QueryResult::end() { return _lineNums->end(); } ostream& print(ostream& os, const QueryResult& qr) { //打印搜索结果 os << "\"" << qr._word << "\" " << "occurs " << qr._lineNums->size() << " times." << endl; for (auto no : *qr._lineNums) { os << "\t(line " << no + 1 << ") " << *(qr._lines->begin() + no) << endl; } return os; } //================更加丰富的查询方法================ ostream& operator<<(ostream& os, const Query& query) { //重载输出运算符,用来打印Query::rep()的返回值 return os << query.rep(); } //========== Query_base ========= Query_base::Query_base() { /* cout << "Query_base()" << endl; */ } //=========== Query ============ //为用户提供调用接口 Query::Query(const string& word) :_query(new WordQuery(word)) { /* cout << "Query(const string &)" << endl; */ } Query::Query(shared_ptr<Query_base> query) : _query(query) { /* cout << "Query(shared_ptr<Query_base> )" << endl; */ } //提供给用户的调用接口 QueryResult Query::eval(const TextQuery& tq) const { return _query->eval(tq);//虚调用,动态链编 } string Query::rep() const { return _query->rep();//虚调用 } //============= WordQuery ============ //查找给定的queryWord,实际在TextQuery对象上进行查询操作的唯一一个类 WordQuery::WordQuery(const string& queryWord) :_queryWord(queryWord) { /* cout << "WordQuery(const string &)" << endl; */ } //覆盖eval和rep QueryResult WordQuery::eval(const TextQuery& tq) const { //直接调用前面的即可 return tq.query(_queryWord); } string WordQuery::rep() const { //直接返回要查询的单词即可 return _queryWord; } //============ NotQuery ============ //查询不包含某个单词的情况 Query operator~(const Query& rhs) { return shared_ptr<Query_base>(new NotQuery(rhs)); } NotQuery::NotQuery(const Query& q) :_query(q) {} QueryResult NotQuery::eval(const TextQuery& tq) const { //返回非的查询结果 QueryResult hasRes = _query.eval(tq);//包含queryWord的行 //存放最终不包含queryWord的行的set,初始为空 shared_ptr<set<line_no>> retLines = make_shared<set<line_no>>(); //遍历所有行,和hasRes对比,不在hasRes的行就是符合条件的,存到retLines中 auto beg = hasRes.begin(); auto end = hasRes.end(); auto sz = hasRes.linesSize(); for (line_no i = 0; i < sz; ++i) { if (beg == end || *beg != i) { //当前遍历到的行,不在hasRes中,保存到retLines中 retLines->insert(i); } else if (beg != end) { //当前遍历到的在hasRes中,继续获取下一行(如果存在) ++beg; } } return QueryResult(rep(), retLines, hasRes.get_lines()); } string NotQuery::rep() const { return "~(" + _query.rep() + ")"; } //========end of NotQuery============ //========== BinaryQuery ============ //与或查询的抽象基类,保存运算符左右两个运算对象 BinaryQuery::BinaryQuery(const Query& lhs, const Query& rhs, const string& op) :_lhs(lhs) , _rhs(rhs) , _opSym(op) {} string BinaryQuery::rep() const { return "(" + _lhs.rep() + " " + _opSym + " " + _rhs.rep() + ")"; } //=========== AndQuery ============ //与运算 Query operator&(const Query& lhs, const Query& rhs) { //重载"&"运算符 return shared_ptr<Query_base>(new AndQuery(lhs, rhs));//递归 } AndQuery::AndQuery(const Query& lhs, const Query& rhs) :BinaryQuery::BinaryQuery(lhs, rhs, "&") { /* cout << endl << "AndQuery(const Query &, const Query &)" << endl << endl; */ } QueryResult AndQuery::eval(const TextQuery& tq) const { //取两个集合的交集 QueryResult lRet = _lhs.eval(tq); QueryResult rRet = _rhs.eval(tq); //要动态分配哦 shared_ptr<set<line_no>> retLines = make_shared<set<line_no>>(); //取交集,调用标准库算法 set_intersection 来合并两个集合 set_intersection(lRet.begin(), lRet.end(), rRet.begin(), rRet.end(), inserter(*retLines, retLines->begin())); return QueryResult(rep(), retLines, lRet.get_lines()); } //=========== OrQuery ============= //或运算 Query operator|(const Query& lhs, const Query& rhs) { return shared_ptr<Query_base>(new OrQuery(lhs, rhs)); } OrQuery::OrQuery(const Query& lhs, const Query& rhs) :BinaryQuery(lhs, rhs, "|") {} QueryResult OrQuery::eval(const TextQuery& tq)const { //返回两个集合的并集 QueryResult lRet = _lhs.eval(tq);//继承的BinaryQuery的 _lhs与_rhs QueryResult rRet = _rhs.eval(tq); //取两个集合的并集 shared_ptr<set<line_no>> retLines = make_shared<set<line_no>>(lRet.begin(), lRet.end()); retLines->insert(rRet.begin(), rRet.end()); return QueryResult(rep(), retLines, lRet.get_lines()); }
//main.cc
此处将用户输入的string数据转为Query,这里是面向过程的写法。
#include "text_query.h" #include <iostream> #include <stack> using namespace std; bool str2Query(const string str, Query& query) { //把用户输入的字符串变换成Query类型 stack<char> st4op; //用来解析表达式的栈,存储运算符 stack<Query> st4Query;//存储单词的栈 string word; //遍历str,借助两个栈来解析str for (auto elem : str) { if (isalpha(elem)) { word += elem;//string是可以加字符的 } else if (elem == '~') { if (word.size()) {//不为0说明左边有字母存在 //word1 ~ word2, 非法表达式,直接返回 cout << "'~'wrong expression!" << endl; return false; } st4op.push(elem); } else if (elem == '(') { //左括号,把'('压栈 st4op.push(elem); } else if (elem == '&') { if (word.size()) { //与运算,如果前一个是单词,压栈,并清空单词 query = Query(word);//动态调用 st4Query.push(query); word.clear(); if (st4op.empty()) { //word1 & word2 st4op.push(elem); continue; } if (st4op.top() == '&') { //如果是word1 & word2 & word3 st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() & query; st4Query.pop(); st4Query.push(query); } else if (st4op.top() == '|') { //如果是word1 | word2 & word3 st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() | query; st4Query.pop(); st4Query.push(query); } } //如果前面什么都没有,非法表达式,直接返回 else if (st4Query.empty()) { cout << "wrong exppression!" << endl; return false; } //如果前面是表达式 ( ... ) else if (st4op.top() == '&') { st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() & query; st4Query.pop(); st4Query.push(query); } else if (st4op.top() == '|') { st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() | query; st4Query.pop(); st4Query.push(query); } st4op.push(elem); } else if (elem == '|') { if (word.size()) { //或运算,如果前一个是单词,压栈,并清空单词 query = Query(word); st4Query.push(query); word.clear(); if (st4op.empty()) { //word1 | word2 st4op.push(elem); continue; } if (st4op.top() == '&') { //如果是word1 & word2 | word3 st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() & query; st4Query.pop(); st4Query.push(query); } else if (st4op.top() == '|') { //如果是word1 | word2 | word3 st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() | query; st4Query.pop(); st4Query.push(query); } } //如果前面什么都没有,非法表达式,直接返回 else if (st4Query.empty()) { cout << "wrong exppression!" << endl; return false; } //如果前面是表达式 ( ... ) else if (st4op.top() == '&') { st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() & query; st4Query.pop(); st4Query.push(query); } else if (st4op.top() == '|') { st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() | query; st4Query.pop(); st4Query.push(query); } st4op.push(elem); } else if (elem == ')') { //如果遇到右括号,弹栈 if (word.size()) { st4Query.push(Query(word)); word.clear(); } if (st4Query.empty()) { //非法表达式,直接返回 cout << "wrong exppression!" << endl; return false; } else { bool flag = false; while (!st4op.empty()) { //求出括号中的表达式 if (st4op.top() == '(') { //正确匹配了括号 flag = true; st4op.pop(); /* st4Query.push(query); */ if (st4op.top() == '~') { //~( ... ) st4op.pop(); query = ~st4Query.top(); st4Query.pop(); st4Query.push(query); } break; } else if (st4op.top() == '&') { //(word1 & word2) st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() & query; st4Query.pop(); st4Query.push(query); } else if (st4op.top() == '|') { st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() | query; st4Query.pop(); st4Query.push(query); } else if (st4op.top() == '~') { // (~word) st4op.pop(); query = ~st4Query.top(); st4Query.pop(); st4Query.push(query); } } if (word.size()) { //(word) query = Query(word); st4Query.push(query); flag = true; word.clear(); } if (!flag) { //如果括号无法正确匹配,表达式有错,直接返回 cout << "wrong exppression!" << endl; return false; } } } }//end of for if (word.size()) { //表达式不带括号的情况 query = Query(word); st4Query.push(query); } while (!st4op.empty() && !st4Query.empty()) { //~word的情况 if (st4Query.empty()) { //非法表达式 cout << "wrong expression!" << endl; return false; } if (st4op.top() == '&') { st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() & query; st4Query.pop(); st4Query.push(query); } else if (st4op.top() == '|') { st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() | query; st4Query.pop(); st4Query.push(query); } else if (st4op.top() == '~') { st4op.pop(); query = ~st4Query.top(); st4Query.pop(); } } //能走到这里的说明程序执行没问题 return true; } void test(ifstream& ifs) { TextQuery tq(ifs); bool flag = false; while (true) { cout << "enter expression to look for, or q to quit: "; string str;//用户输入的表达式 Query q(""); //用来调用接口 if (!getline(cin, str) || str == "q") { break; } //输入了表达式 cin.clear(); flag = str2Query(str, q); if (!flag) { //输入的表达式不正确 cout << "continue..." << endl; continue; } //输入了正确的表达式 print(cout, q.eval(tq)) << endl; } } int main() { ifstream ifs; ifs.open("china-daily.txt"); test(ifs); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义