實習目標 | 1. 使用 string 與 vector
類別物件所提供的功能 2. 使用 C++ 標準函式庫中所提供 sort() 及 find() 演算法 3. 使用 C++ 標準函式庫中所提供物件的介面 4. 熟悉透過介面描述物件功能的抽象化描述方法 |
---|---|
步驟一 | 在 C++ 標準函式庫 (MSDN,
MSDN
TW, cplusplus) 中,
string (MSDN,
cplusplus)
是一個取代 C 中字串 (字元陣列) 的類別(你可以先想成是一種資料型態), vector
(MSDN,
cplusplus,
vector
Member, 建構元範例) 則是取代 C 中陣列的類別,
運用這兩個類別來寫程式的話, 可以省掉很多不必要的錯誤, 同時這是兩個封裝得很好的物件範例。(當然你學會在程式中使用這些東西以後, 並不代表原先你在資料結構裡面學的各種低階架構都沒有用了,
在許多時候你的程式環境並沒有提供給你這些類別或是函式, 或是你需要執行起來非常有效率, 或是使用的記憶體要很小, 你就必須自己撰寫資料結構了)
課本上 string 類別的基本使用範例如下: #include <string> // string #include <string.h> // strcpy() #include <iostream> // cin, cout using namespace std; int main() { string s1, s2; // empty strings string s3 = "Hello, World."; // initialized string s4("I am"); // initialized char dest[100]; s2 = "today"; // string copy s1 = s3 + " " + s4; // concatenation of strings s1 += " 8 "; // appending to a sting cout << s1 + s2 + "!" << endl; // extended ostream strcpy(dest, s1.c_str()); // convert to char array cin >> s1; cout << s1.length(); // or s1.size(); if (s3 + s4 == "Hello, World.I am") { // do something } return 0; } vector 類別的使用範例如下面這個課本上運用 string 與 vector 列印資料及列號的程式 // Thinking in C++ page 105 #include <iostream> // cout #include <fstream> // ifstream #include <vector> // vector #include <string> // string, getline() #include <iomanip> // endl using namespace std; int main() { ifstream inf("AddLineNumber.cpp"); string line; vector<string> lines; while (getline(inf, line)) lines.push_back(line); for (int i=0; i<lines.size(); i++) cout << i << ":" << lines[i] << endl; // 下面是一段運用 vector 容器物件的 iterator 來依序列印所有 // 元素的程式碼 vector<string>::iterator iter; for (iter=lines.begin(); iter!=lines.end(); iter++) cout << iter - lines.begin() << ':' << *iter << endl; return 0; } 迭代器 ( iterator) 是大部分STL 的容器類別裡面都有定義的一個內部類別 (internal class), iterator 型態的變數的 概念和使用方法 和 C 裡面的 "指標變數" 幾乎是一樣的, 以 vector 容器的 iterator 為例, 可以用 *, ->, ++, --, ==, <, -, + 等運算符號, 不過請特別注意這只是概念上相同, 實際上不見得 和 C 裡面的指標一樣是記憶體位址, 你要取得容器物件裡某一個元素的 iterator 時, 不是像 C 裡面直接在變數前面加上 & 來取得, 通常是透過 begin(), end(), find() 等等函式來取得, 但是當你有一個 iterator 時, 你可以運用取值的運算子 * 來取得所指向的資料, 或是箭號運算子 -> 來取得所指向結構的某一欄位, 比較特別的容器像是 priority_queue 並不提供 iterator, 以免客戶程式任意破壞它的封裝設計。 STL 裡還有一個 for_each, 可以用它以及容器的iterator, 巡訪容器裡所有的元素, 使用範例如下: #include <algorithm> // for_each() using namespace std; void printElement(string element) { static int i=0; cout << ++i << ':' << element << endl; } int main() { .... for_each(lines.begin(), lines.end(), printElement); .... } 注意: void printChar(char element) { cout << element; } int main() { .... line = "Hello world\n"; for_each(line.begin(), line.end(), printChar); .... } 3. 你也可以用 vector<vector<int>> matrix(10, vector<int>(5,0)); 定義一個 10x5 的二維陣列, 每一個元素都初始化為 0, 來取代 C 裡面的 int matrix[10][5] = {{0}}; , 如此在程式執行時萬一使用到 matrix[10][0], matrix[8][-1] 時都會得到錯誤的訊息, 這樣子的設計也可以擴充為每一列元素個數不等的二維陣列。 |
步驟二 | 請先修改上面這個課本中的範例程式來處理我們先前的
raw1.dat, 請先宣告一個結構型態
struct DataRecord { int lineNumber; int value; };參考上一個步驟裡的範例, 設定 vector 裡存放的每一元素為 DataRecord, 例如: vector<struct DataRecord> recordArray; 或是 vector<DataRecord> recordArray; (C++ 語法中 struct 關鍵字可省略) 然後運用 iostream 的功能將 raw1.dat 檔案內的資料讀取到 recordArray 陣列中, 將每一筆資料在該檔案內的列號記錄在 lineNumber 那個欄位中, 將每一筆資料的內容記錄在 value 那個欄位中。
DataRecord tmp; int lineNumber = 0; ... inf >> tmp.value; tmp.lineNumber = ++lineNumber; recordArray.push_back(tmp); |
步驟三 | 請運用標準 C++ 的 STL 函式庫中的 sort() (MSDN,
cplusplus)
函式將你在上一步驟讀到的 vector 物件進行排序
請注意: 2. mygreater() 這個函式其實叫做 myorder() 也許比較容易瞭解, 基本上 sort
會呼叫這個 myorder() 函式並且傳遞兩個參數進來, myorder() 函式裡需要判斷哪一個在排序時需要放在前面, 如果第一個要在第二個之前,
myorder() 回傳 true, 如果第二個要在第一個之前, myorder() 回傳 false。 4. 在步驟四裡面還有一種方式可以讓 DataRecord 的物件是可以比較大小的, 就是定義一個 DataRecord::operator<() 成員函式 範例執行程式: 請下載 SortVectorDataRecord.exe 並且下載 raw1.dat 後在命令視窗 cmd 內執行 有同學發現做出來的程式和上面這個程式的結果不太一樣 (如下圖差異好像還不小) 其實左邊是 VC98 編譯出來的程式, 右邊是 VC2010 編譯出來的程式, 兩個用的函式庫的實作顯然不太一樣, 當比較的數值都相同時, 本來順序應該是沒有關係的, 你在學演算法的時候應該有學過 stable sort 的特性, 這兩個實作應該都不是 stable 的, 所得到的順序都不是原本資料裡的順序, 你也許還可以想一下如果我們主要運用 value 來比對, 當 value 相同時運用 lineNumber 來比對的話, 應該就可以得到唯一的結果了, 程式該怎麼改呢? 其它 sort 相關事項請參考下列文章 |
步驟四 | 請在上一步驟所完成的程式中運用 find()
函式在排序過的 vector 資料裡尋找資料 {17, 8} 和 {60, 5} 兩筆資料, 並且列印結果出來 (由於資料已經排序過, 其實可以使用比較有效率的
O(log n) 搜尋方法 binary_search)
範例執行程式: 請下載 SortVectorDataRecord01.exe 並且下載 raw1.dat 後在命令視窗內執行 請注意, 在使用 find() 函式時, 必須替 DataRecord 定義如何比較兩個資料結構是相等的, 這一部份你還沒有學到, 請按照下面的說明修改 struct DataRecord { .... bool operator==(const DataRecord& y); }; bool DataRecord::operator==(const DataRecord& y) { 請比較欄位 value 和 y.value 是相等的, 同時 lineNumber 和 y.lineNumber 是相等的 } Hint: return ((value == y.value) && ...); find() 函式可以搜尋一個運用容器物件 v 的 iterator 指定的區間裡面的資料, 例如: #include <algorithm> using namespace std; ... vector<DataRecord> v; vector<DataRecord>::iterator result; ... DataRecord goal; goal.value = 8; goal.lineNumber = 17; ... result = find(v.begin(), v.end(), goal); if (result != v.end()) cout << result->lineNumber << ":" << result->value << endl;呼叫時需要傳給它三個參數
這個範例中 find() 函式會傳回一個 vector<DataRecord>::iterator 型態的資料, 如果這個資料等於 v.end() 就表示沒有找到, 如果找到的話, 你可以運用它傳回的 iterator 來列印結果, iterator 相當於指向那個找到的結構變數的指標, 所以你可以用 iterator->lineNumber 和 iterator->value 來存取那個找到的結構裡的兩個欄位, 你也可以運用 iter - v.begin() 來得到 iter 目前在容器中的位置 (類似指標運算)。 |
步驟五 | 請將所完成的 專案 (只需保留 .cpp, .h, .sln 以及 .vcxproj 檔案即可; 刪除掉 .suo, .sdf, .filters, .users, debug\ 資料匣, 以及 ipch\ 資料匣下的所有內容) 壓縮起來, 選擇 Lab2-2上傳 , 後面的實習課程可能需要使用這裡所完成的程式 |
請注意 sort 演算法只能用在 vector 這種具有 random access iterator 類別的物件, 而不能用在 list 類別的物件, find 演算法則都可以運用 其它 C++ 標準函式庫之容器物件, STL Containers (b&w) 在 vector 容器上使用預設的 binary_search 演算法 使用 iterator adaptors: ostream_iterator, istream_iterator, reverse_iterator |
回
C++ 物件導向程式設計課程 首頁 製作日期: 03/13/2013 by 丁培毅 (Pei-yih Ting) E-mail: pyting@mail.ntou.edu.tw TEL: 02 24622192x6615 海洋大學 電機資訊學院 資訊工程學系 Lagoon |