前言
本文將講解一個經典的文字查詢程式,對前面所學的容器相關知識進行一個從理論到實際的昇華,同時也對即將學習的物件導向知識來一次初體驗。
程式描述
要求實現這樣一個程式:讀取使用者指定的檔案,然後允許使用者從中查詢某個單詞所在的位置。
一個程式導向的落後的設計思想
將待檢索檔案以行為單位存放到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 else 7 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. 感受物件導向思想