c++ primer 的 textquery 例子,做了好几天。发现对入门c++基础是个很大检测,不像初看时,那么简单。
起码包含了几个知识点,智能指针,值类型智能指针,树的遍历(递归),构造和析构,多态,操作符重载。
贴出代码和一些自己体会。
共有2章完善这个 程序。前一个程序很简单,没什么好说。就是查询文本中,某个单词出现的行号。
后面要求提出组合查询,几个单词同时存在,或再排除某个单数 出现的行号,的逻辑组合 。
自己做了很多遍,用了好几种方法,改来改去,最终发现自己思考的过程其实就是书要讲解的思路。
发现后面发现有几个特别要注意到的地方。
1)书中有个没有特别提出的需求,完成or ,and ,not 组合查询,并当写出一个查询组合,可以方便的运用于任何一个文本数据源。这个要求才会让人必须往多态上想。不然完全不必要用基类和继承处理。
2)本来可以按常规思路,每次计算一个结果,用操作符处理每次结果。现在就要求我们把计算方法延后执行,把执行计算的方法,改为储存方法和此方法所需的数据,也就是储存类的实例。再在需要的时候按照顺序执行方法。
3)编译器根据符号优先级(c++的文法句子),对方法的执行是一个树的遍历过程。所以我们最后我们得到的结果是一个树的根。如,Query tmp=~(Query(star)|Query(little)&Query(Twinkle)); tmp 是树的根节点。
4)用后续遍历法执行每个节点的eval方法。来模拟 编译器对表达式的计算思路。但仔细看的话,会知道方法的执行和当初节点的建立顺序其实不是一样的。不过不妨碍结果。
5)注意二叉树和单子树节点,构造函数的参数是值传递。Node_2L(Query _lhs, Query _rhs,const string & _opstr);
这样构造的时候,会对lhs,rhs进行直copy。直copy会引发lhs,rhs中,自己的lhs,rhs直copy。一层一层。如此引用型计数器use,才能正常得到引用次数。临时对象析构时,才不会delete handle class,所指向的对象。
6)display的过程是一个树中序遍历的过程。
完整下载,ide:code block.
部分代码,方便快速查看。
textquery.h
#ifndef TEXTQUERY_H_INCLUDED #define TEXTQUERY_H_INCLUDED #include "head.h" typedef vector<string>::size_type line_no; typedef map<string,set<line_no> >::iterator Type_mIt; typedef set<line_no>::iterator Type_sIt; class TextQuery { public: TextQuery(){} void readfile(ifstream &is) { store_file(is); build_map(); } set<line_no> run_query(const string& word) const { return word_map.find(word)->second; } string text_line(line_no line_n) const { if(line_n<=lines_of_text.size()) { return lines_of_text[line_n]; } else { throw string("out of range!"); } } map<string,set<line_no> >& getmap() { return word_map; } int getsumLine()const { return lines_of_text.size(); } set<line_no> getAllset()const { set<line_no> tmp; for(line_no i=0;i!=lines_of_text.size();++i) { tmp.insert(i); } return tmp; } ~TextQuery() { } private: //data vector<string> lines_of_text; map<string,set<line_no> > word_map; //text void store_file(ifstream& is) { string textLine; if (!is.is_open()) {cout << "Error opening file";} while (getline(is,textLine)) { lines_of_text.push_back(textLine); } } void build_map() { for(line_no i=0;i!= lines_of_text.size();i++) { istringstream words(lines_of_text[i]); string word; while(words >> word) { word_map[word].insert(i); word=""; } } } }; #endif // TEXTQUERY_H_INCLUDED
head.h
#ifndef HEAD_H_INCLUDED #define HEAD_H_INCLUDED //标准库--》类型缩写--》类定义--》cpp文档,函数申明--》 通用函数。--》其他函数。 #include <set> #include <string> #include <vector> #include <map> #include <set> #include <iostream> #include <fstream> #include <cctype> #include <cstring> #include <sstream> #include <algorithm> using namespace std; typedef vector<string>::size_type line_no; //main.cpp void linkqureyf2(); void linkqureyf(); void simQuery(); void textquery(); void opqueryF(); void outLine(ostream& of,const string &content); void print_result(const map<string,set<line_no> >& wordsLine); void print_result2(const set<line_no> &wordsLine); void treefun(); //simple set<line_no> OrSet(const set<line_no>& ls,const set<line_no>& rs); set<line_no> AndSet(const set<line_no>& ls,const set<line_no>& rs); set<line_no> NotSet(const set<line_no>& sourceSet,const set<line_no>& AllSet); #endif // HEAD_H_INCLUDED
main.cpp
#include "head.h" #include "textquery.h" #include "simplequery.h" #include "opquery.h" #include "LinkQuery.h" #include "lineLinkQuery.h" #include "TreeQuery.h" vector<u_p> LinkQuery::funs=vector<u_p>(); bool Query::debug=false; int main() { //textquery(); //simQuery(); //opqueryF(); //linkqureyf(); treefun(); return 0; } //依据表达式用最恰当的树的数据结构,来储存实例。建立树。 //采用后序遍历法,访问每个节点,执行方法,来达到表达式同样的结果(尽管顺序其实不是严格一致)。 void treefun() { ifstream infile("littlestar.txt"); TextQuery file; file.readfile(infile); outLine(cout,"words-lineNo mapping....................."); print_result(file.getmap()); string little="little"; string Twinkle="Twinkle"; string star="star"; Query tmp=~(Query(star)|Query(little)&Query(Twinkle)); cout<<tmp.NodeName()<<endl; cout<<tmp.display(cout)<<endl; print_result2(tmp.Eval(file)); } void textquery() { ifstream infile("littlestar.txt"); TextQuery file; file.readfile(infile); outLine(cout,"words-lineNo mapping....................."); print_result(file.getmap()); set<line_no> ret= file.run_query("little"); outLine(cout,"‘little line-no:‘....................."); print_result2(ret); } void outLine(ostream& of,const string &content) { of<<"***********"<<content<<"***********"<<endl; } void print_result(const map<string,set<line_no> >& wordsLine) { map<string,set<line_no> >::const_iterator beg1; for(beg1=wordsLine.begin();beg1!=wordsLine.end();beg1++) { cout<<beg1->first<<":"; set<line_no>::iterator bega=beg1->second.begin(); for(line_no i=0;i<beg1->second.size();i++,bega++) { cout<<*bega+1<<","; } cout<<endl; } } void print_result2(const set<line_no> &wordsLine) { set<vector<string>::size_type>::iterator beg=wordsLine.begin(); while(beg!=wordsLine.end()) { cout<<*beg+1<<","; ++beg; } cout<<endl; }
treequery.h
#ifndef TREEQUERY_H_INCLUDED #define TREEQUERY_H_INCLUDED #include "head.h" #include "textquery.h" //base class //所有方法,数据都private,让handle为友类访问,操作符重载方法也访问。 //注意看构造函数,//所以const 和&,需要认真理解,完成例子后,再用时间熟悉下const。 class Query; ostream& operator<<(ostream &os, const Query& q); Query operator&(const Query& lhs,const Query& rhs); Query operator|(const Query& lhs,const Query& rhs); Query operator~(const Query& lhs); class base_TNode { protected: virtual ~base_TNode(); private://方法全部隐藏,且虚函数。由handle class来代理,并根据实际对象,调用自己的。 virtual set<line_no> Eval(const TextQuery& _file)=0; virtual string NodeName()const=0;//因为此方法会被派生类的数据成员lhsrhs调用。 而Node_2L,和notnode中的数据成员lhs,rhs,都为const。所以这里要const。 virtual ostream& display(ostream& os)const=0;//同上。 friend class Query; friend Query operator&(const Query& lhs,const Query& rhs); friend Query operator|(const Query& lhs,const Query& rhs); friend Query operator~(const Query& lhs); }; base_TNode::~base_TNode(){} //handle class,有base_TNode,基类的指针成员。 //作为功能之一:智能指针,所有使用基类指针的地方,必须使用Query。以便计数器正确。 //作为功能之二,handle。handle class 必须代理派生类方法。 class Query { public: Query(const Query& up); Query& operator=(const Query& up); ~Query(); Query(const string& _word); set<line_no> Eval(const TextQuery& _file)const; string NodeName() const;//连派生类访问基类的方法。都要通过handle class。 ostream& display(ostream& os)const; //ostream & operator<<(const Query& _qy); static bool debug; private: Query(base_TNode* _p);//仅仅给操作符重载使用,隐藏,把操作符重载方法,加为友元。 base_TNode* p;//必须隐藏指针,基类的方法都由handle class。代理。 unsigned int * use; void del(); friend Query operator|(const Query& lhs,const Query& rhs); friend Query operator&(const Query& lhs,const Query& rhs); friend Query operator~(const Query& lhs); }; //leaf class Leaf:public base_TNode { private: Leaf(const string& _word);//隐藏派生类,只给handle class 友类来访问。 string NodeName()const; string word; set<line_no> Eval(const TextQuery&); ostream& display(ostream& os)const; friend class Query; }; //friend Query::Query(base_TNode* _p); Leaf::Leaf(const string& _word):word(_word){} string Leaf::NodeName()const { return word; } set<line_no> Leaf::Eval(const TextQuery& _file) { //if(no left child and no right child) //看成后序遍历的,递归终结的临界点。 return _file.run_query(word); } ostream& Leaf::display(ostream& os)const { return os<<word; } //base of 2-child node class Node_2L:public base_TNode { protected: Node_2L(Query _lhs, Query _rhs,const string & _opstr);//参数值传递Query,(handle class) 对象。这样构造的时候,会对lhs,rhs进行直copy。 //直copy会引发lhs,rhs中,自己的lhs,rhs直copy。一层一层。如此引用型计数器use,才能正常得到引用次数。临时对象析构时,才不会delete handle class,所指向的对象。 const Query lhs; const Query rhs; ostream& display(ostream& os)const; private: string NodeName()const; string opstr; }; Node_2L::Node_2L(Query _lhs, Query _rhs,const string & _opstr):lhs(_lhs),rhs(_rhs),opstr(_opstr){} string Node_2L::NodeName()const { return "("+lhs.NodeName()+opstr+rhs.NodeName()+")"; } ostream& Node_2L::display(ostream& os)const { //按照简单的下面方式得不到正确结果,会把lhs.display(os)当成一个地址打出来。 //用简单方法,更容易维护。比如用NodeName方法同样的效果。那就必须重新解释<<的含义。重载<<。 //return os<<"("<<lhs.display(os)<<opstr<<rhs.display(os)<<")"; return os << "(" << lhs << " " << opstr << " "<< rhs << ")"; } //and node class AndNode:public Node_2L { private: AndNode( Query _lhs, Query _rhs); set<line_no> Eval(const TextQuery& _file); friend Query operator&(const Query& lhs,const Query& rhs);//操作符重载需要访问。 }; AndNode::AndNode(Query _lhs, Query _rhs):Node_2L(_lhs,_rhs,"&"){} set<line_no> AndNode::Eval(const TextQuery& _file) { set<line_no> leftret=lhs.Eval(_file); set<line_no> rightret=rhs.Eval(_file); return AndSet(leftret,rightret); } //or node class OrNode:public Node_2L { private: OrNode( Query _lhs, Query _rhs); set<line_no> Eval(const TextQuery& _file); friend Query operator|(const Query& lhs,const Query& rhs);//操作符重载需要访问。 }; OrNode::OrNode( Query _lhs, Query _rhs):Node_2L(_lhs,_rhs,"|"){} set<line_no> OrNode::Eval(const TextQuery& _file) { set<line_no> leftret=lhs.Eval(_file); set<line_no> rightret=rhs.Eval(_file); return OrSet(leftret,rightret); } //not node class NotNode:public base_TNode { private: NotNode(Query lhs); set<line_no> Eval(const TextQuery& _file); ostream& display(ostream& os)const; string NodeName()const; const Query lhs; friend Query operator~(const Query& lhs); }; NotNode::NotNode(Query _lhs):lhs(_lhs){} string NotNode::NodeName()const { return "~("+lhs.NodeName()+")"; } set<line_no> NotNode::Eval(const TextQuery& _file) { set<line_no> leftret=lhs.Eval(_file); return NotSet(leftret,_file.getAllset()); } ostream& NotNode::display(ostream& os)const { ////??????????????????????? return os << "~(" << lhs << ")"; } //定义 Query::Query(base_TNode * _p):p(_p),use(new unsigned int(1)){if(Query::debug){cout<<p->NodeName()<<": ‘*p‘ constr to query:"<<p<<" use:."<<*use<<endl;}} Query::Query(const Query& up) { p=up.p; use=up.use; ++*use; if(Query::debug){cout<<p->NodeName()<<": ref:"<<p<<" use:."<<*use<<endl;} } Query& Query::operator=(const Query& up) { ++*up.use; del(); p=up.p; use=up.use; if(Query::debug){cout<<p->NodeName()<<": copy =:"<<p<<" use:."<<*use<<endl;} return *this; } Query::~Query() { string tmp=p->NodeName(); if(Query::debug){cout<<tmp<<": before delete1 :"<<p<<" use:."<<*use<<endl;} del(); } void Query::del() { string tmp=p->NodeName(); if(Query::debug){cout<<tmp<<": before delete2 :"<<p<<" use:."<<*use<<endl;} if(--*use==0) { delete p; delete use; if(Query::debug){cout<<tmp<<": delete end"<<endl;} } else { if(Query::debug){cout<<tmp<<": delete end(just sub use.) :"<<p<<" use:."<<*use<<endl;} } } string Query::NodeName()const { return p->NodeName(); } Query::Query(const string& _word) { p=new Leaf(_word); use=new unsigned int(1); if(Query::debug){cout<<p->NodeName()<<": query ‘word‘ construct "<<p<<" use:."<<*use<<endl;} } set<line_no> Query::Eval(const TextQuery& _file)const { return p->Eval(_file); } ostream& Query::display(ostream& os)const { return p->display(os); } //end query 定义 Query operator&(const Query& lhs,const Query& rhs) { // base_TNode *p=new AndNode(lhs,rhs); // if(Query::debug){cout<<p->NodeName()<<": pointer create "<<p<<endl;} // Query tmp=Query(p); // // return tmp; return Query(new AndNode(lhs,rhs));//raii 原则,获得资源即初始。 } Query operator|(const Query& lhs,const Query& rhs) { // base_TNode *p=new OrNode(lhs,rhs); // if(Query::debug){cout<<p->NodeName()<<": pointer create "<<p<<endl;} // Query tmp=Query(p); // // return tmp; return Query(new OrNode(lhs,rhs));//raii 原则,获得资源即初始。 } Query operator~(const Query& lhs) { // base_TNode *p=new NotNode(lhs); // if(Query::debug){cout<<p->NodeName()<<": pointer create "<<p<<endl;} // Query tmp=Query(p); // // return tmp; return Query(new NotNode(lhs));//raii 原则,获得资源即初始。 } inline ostream& operator<<(ostream &os, const Query& q) { return q.display(os); } #endif // TREEQUERY_H_INCLUDED