本文總結摘錄了《c++ primer》中的一個大例子---文字查詢程式。主要目的在於學習大師的編程規範。
程式要求:讀取使用者指定的任意文字檔,然後允許使用者從該檔案中尋找單詞。查詢的結果是該單詞出現的次數,並列出每次出現所在的行。如果某單詞在同一行中多次出現,程式將只顯示該行一次,行號按升序顯示。
分析:
- 輸入為檔案名稱;
- 必須將一行作為一個整體,由行號輸出所在行;
- 必須每一行都分解為各個單詞,並記錄單詞所在行,對於單詞的查詢返回所有所在行;
- 此處主要考察容器的使用;
求解:
- 使用vector來儲存整行,vector中一個元素就是一行;
- 使用map來以單詞為關鍵字,以所在行的集合為衛星資料;
下面是所包含的的標頭檔:
#include<utility>#include<set>#include<map>#include<vector>#include<string>#include<iostream>#include<fstream>#include<sstream>using namespace std;
下面是主要類TextQuery:
class TextQuery { public: //typedef to make declarations easier typedef std::vector<std::string>::size_type line_no; typedef std::set<line_no>::const_iterator set_it; /* interface: * read_file() builds internal data structures for the given file * run_query() finds the given word and returns "set" of lines on which it appears * text_line() return a requested line from the input file */ void read_file(std::ifstream& is) { store_file(is); build_map();} std::pair<set_it,set_it> run_query(const std::string& ) const; std::string text_line(line_no) const; private: //utility functions used by read_file() void store_file(std::ifstream& ); //store input file void build_map(); //associated each word with a set of line numbers; std::vector<std::string> lines_of_text; //remember the whole input file std::map<std::string, std::set<line_no> > word_map; };
下面是輔助函數make_plural和open_file:
/*此函數提供一個計數器,一個單詞word和單詞結束符ending, 當計數器值大於1時,返回單詞的複數版本*/ std::string make_plural(size_t ctr,const std::string& word, const std::string& ending) { return (ctr==1)?word :word+ending; } //開啟給定檔案 std::ifstream& open_file(std::ifstream& in,const std::string& file) { in.close();//close in case it was already open in.clear();//clear any existing errors; in.open(file.c_str()); return in; }
下面是類中的各個函數實現,此處run_query的實現採用的是作者的第二種建議,返回一對迭代器:
//第一步:儲存輸入檔案 void TextQuery::store_file(ifstream& is) { string textline; while( getline(is,textline) ) lines_of_text.push_back(textline); } //第二步:建立單詞map容器 void TextQuery::build_map() { for(line_no line_num=0;line_num!=lines_of_text.size();++line_num){ istringstream line(lines_of_text[line_num]); string word; while(line>>word){ //add this line number to the set; //其中word_map[word]是一個集合,然後insert word_map[word].insert(line_num); } } } //第三步:支援查詢 //返回set的一對迭代器 pair<TextQuery::set_it,TextQuery::set_it> TextQuery::run_query(const string& query_word) const { //此處需要注意不能用下標操作直接查詢,以防止插入不存在的元素 map<string,set<line_no> >::const_iterator loc=word_map.find(query_word); return make_pair((loc->second).begin(),(loc->second).end()); }//第四步: 輸出結果 void print_results( pair<TextQuery::set_it,TextQuery::set_it>& its, const string& sought,const TextQuery& file) { typedef set<TextQuery::line_no> line_nums; line_nums::size_type size=0; for(TextQuery::set_it beg=its.first;beg!=its.second;++beg) ++size; cout<<"\n"<<sought<<" occurs "<<size<<" "<<make_plural(size,"time","s")<<endl; line_nums::const_iterator it=its.first; for(;it!=its.second;++it){ cout<<"\t(line "<<(*it)+1<<")"<<file.text_line(*it)<<endl; } } string TextQuery::text_line(line_no line) const { if(line<lines_of_text.size()) return lines_of_text[line]; throw std::out_of_range("Line number out of range"); }
測試程式如下:
int main() { ifstream infile; string filename; if(!(cin>>filename) || !open_file(infile,filename)){ cerr<<"NO input file!"<<endl; return EXIT_FAILURE; } TextQuery tq; tq.read_file(infile); while(true){ cout<<"enter word to look for, or q to quit: "; string s;cin>>s; if(!cin || s=="q") break; pair<TextQuery::set_it,TextQuery::set_it> its=tq.run_query(s); print_results(its,s,tq); }return 0; }
擴充上面的文字查詢程式,要求:
- 尋找單個單詞的查詢,按升序顯示所有包含該單詞的行;
- “非”查詢,使用~操作符,顯示所有不匹配行;
- “或”查詢,使用|操作符,顯示與兩個查詢條件中任意一個匹配的所有行;
- “與”查詢,使用&操作符,顯示與兩個查詢條件都匹配的所有行;
- 可以組合這些操作符;
設計類如下:
我們使用時:
Query q=Query(“ ”) & Query(“ ”)|Query()
這個使用方式告訴我們,使用者級代碼將不能直接使用我們的繼承層次,我們只能定義一個名為Query的控制代碼類,來隱藏繼承層次。使用者代碼將根據控制代碼執行,使用者代碼只能間接操縱Query_base對象。
以Query q=Query("fiery") & Query("bird") |Query("wind")為例:
產生10個對象:5個Query_base對象及其相關聯的控制代碼。
此處直接截取書中代碼:
// private, abstract class acts as a base class for concrete query types class Query_base { friend class Query; protected: typedef TextQuery::line_no line_no; virtual ~Query_base() { } private: // eval returns the |set| of lines that this Query matches virtual std::set<line_no> eval(const TextQuery&) const = 0; // display prints the query virtual std::ostream& display(std::ostream& = std::cout) const = 0; };
Query控制代碼類:
// handle class to manage the Query_base inheritance hierarchy class Query { // these operators need access to the Query_base* constructor friend Query operator~(const Query &); friend Query operator|(const Query&, const Query&); friend Query operator&(const Query&, const Query&); public: Query(const std::string&); // builds a new WordQuery // copy control to manage pointers and use counting Query(const Query &c): q(c.q), use(c.use) { ++*use; } ~Query() { decr_use(); } Query& operator=(const Query&); // interface functions: will call corresponding Query_base operations std::set<TextQuery::line_no> eval(const TextQuery &t) const { return q->eval(t); } std::ostream &display(std::ostream &os) const { return q->display(os); } private: Query(Query_base *query): q(query), use(new std::size_t(1)) { } Query_base *q; std::size_t *use; void decr_use() { if (--*use == 0) { delete q; delete use; } } };
重載操作符:
inline Query operator&(const Query &lhs, const Query &rhs) { return new AndQuery(lhs, rhs); } inline Query operator|(const Query &lhs, const Query &rhs) { return new OrQuery(lhs, rhs); } inline Query operator~(const Query &oper) { return new NotQuery(oper); }
輸出操作符:
inline std::ostream& operator<<(std::ostream &os, const Query &q) { return q.display(os); }
WordQuery類:
class WordQuery: public Query_base { friend class Query; // Query uses the WordQuery constructor WordQuery(const std::string &s): query_word(s) { } // concrete class: WordQuery defines all inherited pure virtual functions std::set<line_no> eval(const TextQuery &t) const { return t.run_query(query_word); } std::ostream& display (std::ostream &os) const { return os << query_word; } std::string query_word; // word for which to search };
NotQuery類:
class NotQuery: public Query_base { friend Query operator~(const Query &); NotQuery(Query q): query(q) { } // concrete class: NotQuery defines all inherited pure virtual functions std::set<line_no> eval(const TextQuery&) const; std::ostream& display(std::ostream &os) const { return os << "~(" << query << ")"; } const Query query; }; set<TextQuery::line_no> NotQuery::eval(const TextQuery& file) const { // virtual call through the Query handle to eval set<TextQuery::line_no> has_val = query.eval(file); set<line_no> ret_lines; // for each line in the input file, check whether that line is in has_val // if not, add that line number to ret_lines for (TextQuery::line_no n = 0; n != file.size(); ++n) if (has_val.find(n) == has_val.end()) ret_lines.insert(n); return ret_lines; }
Binary純虛類:
class BinaryQuery: public Query_base { protected: BinaryQuery(Query left, Query right, std::string op): lhs(left), rhs(right), oper(op) { } // abstract class: BinaryQuery doesn't define eval std::ostream& display(std::ostream &os) const { return os << "(" << lhs << " " << oper << " " << rhs << ")"; } const Query lhs, rhs; // right- and left-hand operands const std::string oper; // name of the operator };
OrQuery類:
class OrQuery: public BinaryQuery { friend Query operator|(const Query&, const Query&); OrQuery(Query left, Query right): BinaryQuery(left, right, "|") { } // concrete class: OrQuery inherits display and defines remaining pure virtual std::set<line_no> eval(const TextQuery&) const; }; set<TextQuery::line_no> OrQuery::eval(const TextQuery& file) const { // virtual calls through the Query handle to get result sets for the operands set<line_no> right = rhs.eval(file), ret_lines = lhs.eval(file); // destination to hold results // inserts the lines from right that aren't already in ret_lines ret_lines.insert(right.begin(), right.end()); return ret_lines; }
AndQuery類:
class AndQuery: public BinaryQuery { friend Query operator&(const Query&, const Query&); AndQuery (Query left, Query right): BinaryQuery(left, right, "&") { } // concrete class: And Query inherits display and defines remaining pure virtual std::set<line_no> eval(const TextQuery&) const; }; set<TextQuery::line_no> AndQuery::eval(const TextQuery& file) const { // virtual calls through the Query handle to get result sets for the operands set<line_no> left = lhs.eval(file), right = rhs.eval(file); set<line_no> ret_lines; // destination to hold results // writes intersection of two ranges to a destination iterator // destination iterator in this call adds elements to ret set_intersection(left.begin(), left.end(), right.begin(), right.end(), inserter(ret_lines, ret_lines.begin())); return ret_lines; }