Lab 2-2: Sample program using vector, string,
sort,and find

 
實習目標 1. 使用 string 與 vector 類別物件所提供的功能
2. 使用 C++ 標準函式庫中所提供 sort() 及 find() 演算法
3. 使用 C++ 標準函式庫中所提供物件的界面
4. 熟悉透過界面描述物件功能的抽象化描述方法
 
步驟一 C++ 的標準函式庫中, string 是一個取代 C 中字串 (字元陣列) 的類別(你可以先想成是一種資料型態), vector (建構元範例) 則是取代 C 中陣列的類別, 運用這兩個類別來寫程式的話, 可以省掉很多不必要的錯誤。(當然你學會在程式中使用這些東西以後, 並不代表原先你在資料結構裡面學的各種低階架構都沒有用了, 在許多時候你的程式環境並沒有提供給你這些類別或是函式, 或是你需要執行起來非常有效率, 或是使用的記憶體要很小, 你就必須自己架構資料結構了)

string 類別的基本使用範例如下:

#include <string>
using namespace std;

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
}

vector 類別的使用範例如下面這個課本上運用 string 與 vector 列印資料及列號的程式

// Thinking in C++ page 105
#include <iostream>
#include <vector>
#include <string>
#include <iomanip>
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 << endl;

    return 0;
}
iterator 是每一個 STL 的容器類別裡面都有定義的一個內部類別 (internal class), iterator 型態的變數的概念和使用方法 和 C 裡面的 "指標變數" 幾乎是一樣的, 可以用 *, ->, ++, -- 等運算符號, 不過請特別注意這只是概念上相同, 實際上不見得就是記憶體位址, 你要取得容器物件裡某一個元素的 iterator 時, 不是像 C 裡面直接在變數前面加上 & 來取得, 通常是透過 begin(), end(), find() 等等函式來取得。
步驟二 請先修改上面這個課本中的範例程式來處理我們先前的 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() 函式將你在上一步驟讀到的 vector 物件進行排序
  • 要使用 sort() 函式的話, 你需要 #include <algorithm>
  • 然後請注意看 MSDN Library 中的 alg_sort.cpp 範例, 節錄如下:
    #include <algorithm>
    using namespace std;
    ...
    bool UDgreater(int elem1, int elem2) 
    {
       return elem1 > elem2;
    }
    ...
    vector<int> v1;
    ...
    sort(v1.begin(), v1.end());
    ...
    sort(v1.begin(), v1.end(), UDgreater);
    ...
    
    由於這個範例程式中 vector 的基本元素是 int, 所以才可以用 sort(v1.begin(), v1.end()) 這樣子兩個參數的型式來排序, 我們現在的應用程式中 vector 的基本元素是 DataRecord, 所以必需用第二種排序的方法,也就是 sort(v1.begin(), v1.end(), UDgreater) 來排序, 其中第三個參數是一個所謂 Binary Predicate 的函式之指標, 這個函式需要接受兩個參數並且進行比較大小的動作, 它的 prototype 可以如下述:
    bool greater(DataRecord elem1, DataRecord elem2);
    或是
    bool greater(DataRecord &elem1, DataRecord &elem2);
    或是
    bool greater(const DataRecord elem1, const DataRecord elem2);
    或是
    bool greater(const DataRecord &elem1, const DataRecord &elem2);
    或是
    int greater(const DataRecord &elem1, const DataRecord &elem2);
        
    請完成這個 greater 的函式, 完成排序的程式, 並且將結果加上列號與原始的列號一起印出來
範例執行程式: 請下載 SortVectorDataRecord.exe 並且下載 raw1.dat 後在命令視窗內執行
步驟四 請在上一步驟所作出來的程式中運用 find() 函式在排序過的 vector 資料裡尋找資料 {17, 8} 和 {60, 5} 兩筆資料, 並且列印結果出來

範例執行程式: 請下載 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 來呼叫, 例如:

        #include <algorithm>
        using namespace std;
        ...
        vector<DataRecord> v;
        ...
        DataRecord goal;
        goal.value = 8;
        goal.lineNumber = 17;
        ...
        find(v.begin(), v.end(), goal);
呼叫時需要傳給它三個參數

  1. 容器物件裡存放的多筆資料的起始搜尋位置, 如果你希望由第一筆資料開始比對, 你可以傳 v.begin()
  2. 容器物件裡存放的多筆資料的結束搜尋位置, 如果你希望由第一筆資料開始比對, 你可以傳 v.end()
  3. 想要搜尋的資料

find() 函式會傳回一個 vector<...>::iterator 型態的資料, 如果這個資料等於 v.end() 就表示沒有找到, 如果找到的話, 你可以運用它傳回的 iterator 來列印結果, iterator 相當於指向那個找到的結構變數的指標, 所以你可以用 iterator->lineNumber 和 iterator->value 來存取那個找到的結構裡的兩個欄位, 你也可以運用 iter - v.begin() 來找到 iter 目前在容器中的位置。

步驟五 請將所完成的 project (去掉 debug/ 資料匣下的所有內容) 壓縮起來儲存在 cyber 上你的帳號內, 後面的實習課程可能需要使用這裡所完成的程式
  其他相關事項請參考下列文章

C++ 物件導向程式設計課程 首頁

製作日期: 02/16/2004 by 丁培毅 (Pei-yih Ting)
E-mail: pyting@cs.ntou.edu.tw TEL: 02 24622192x6615
海洋大學 工學院 資訊工程系 Lagoon