實習目標 | 1. 練習使用 new / delete 配置記憶體
|
---|---|
步驟一 | 在上課時我們談到在 C++ 中同一個函式名稱可以定義多個不同的函式, 只要函式所接受的參數有一些不一樣就可以了,
例如:
void add(int, int); void add(int); void add(float, float);這些都是可以同時定義的函式, 你在呼叫這些函式的時候 compiler 不會產生任何混淆, 例如: int x, y; float r, s; add(x); add(x, y); add(10, y); add(r, s); add(r, 10);compiler 都可以根據參數的型態和參數的個數去找到正確的函式來呼叫 在這個練習的第一部份裡我們繼續實習 3.1裡 Complex 類別的設計, 替它加上幾個 overloaded functions 請下載你上一次實習時的程式檔案, 產生一個新的 專案, 把 complex.h 和 complex.cpp 拷貝進來, 並且加入 專案 中 |
步驟二 | 我們先寫一小段應用程式, 請使用 new[] 配置一個有 10 個元素的 Complex 物件陣列, 寫一個迴圈將其中資料設為 (1+i), (2+2i), (3+3i), ..., (10+10i), 再寫一個迴圈將它們都加起來, 將加總結果用 print() 成員函式列印出來, 最後用 delete[] 刪除所配置的物件陣列以及它們所佔用的記憶體 |
步驟三 | 請參考實習
3.1中第五步驟, 在那裡我們定義了一個 setValue(double, double) 的函式, 現在我們要定義一個 setValue(Complex
&src) 的函式,這個函式最主要的目的是為了拷貝一個複數物件 src 到自己這個物件裡, 用法如下:
Complex x; .... // 計算或是設定 x 的內容 Complex y; y.setValue(x); // 由 x 中拷貝資料到 y 中 (把 y 物件的內容設定為 x 物件的內容) assert(x.equal(y, 1e-10));請完成 setValue(Complex &src) 成員函式的定義 (在函式中應該複製 src.m_real 和 src.m_imaginary 的欄位), 並且在 main() 或是 unitTest() 函式中測試此成員函式的功能 請注意在這裡我們已經開始使用方便的參考變數來取代指標變數了 |
步驟四 | 在我們進行下一步驟之前我們需要先修改 實習
3.1 中第九步驟所作的 void Complex::print() 成員函式, 這個函式本來會把類別裡的資料固定列印到螢幕上去,
現在我們稍微修改一下讓它可以更一般化, 同樣一個函式可以列印到不同的資料串流裡
(螢幕串流或是檔案串流)
請將函式參數型態改為 void Complex::print(ostream &out);在函式內請將資料寫到傳入的串流變數 out 內, 而不要固定寫到 cout 去。 如果你覺得使用相同函式名稱的概念已經很清楚的話, 也可以直接定義同名的函式, 就是 overload print() 這個函式 注意: print 函式的參數型態是 ostream, (需要 #include <ostream>, 也需要 using namespace std;) 雖然希望接收的參數希望是類似下面定義的 outfile 檔案串流物件, 型態是 ofstream: ofstream outfile("outputfile.txt"); Complex x; ... x.print(outfile); 但是有的時候也希望可以直接列印到螢幕去, 到字串裡, 甚至到網路遠端的裝置去, 例如 x.print(cout)所以我們就不用 ofstream 而用它的父類別 ostream,這裡有繼承的概念, 但是目前還沒有講到,你可以想像說 "類別" 是由很多具有共同的特徵共同的特徵同類型物件所抽象化出來的, 我們定義了某一個類別以後就用這個類別來描述所有同類型的物件, "父類別" 則是從很多同類型的 "類別" 抽象化出來的共通特性和介面; ostream 是 ofstream 和 ostrstream 共同的父類別, 同時 cout 也是一個 ostream 型態的物件, 所以 ostream 具有所有輸出串流的共同特性, 我們只需要說參數是 ostream 型態的, 實際上傳遞 cout 或是 outfile 給這個函式時參數型態都是對的, 沒有型態不合的問題。進一步更完整的描述要等到我們課程進行到繼承 (inheritance) 時才會說明。 |
步驟五 | 接下來我們要練習覆蓋
(overload) 一個全域 (global) 的函式 operator<<(), 在 ostream
類別裡本來就有定義一個成員函式
ostream &ostream::operator<<(int);這個函式最主要是給下面的敘述使用的: int x; cout << x; 對 C++ 編譯器來說 cout << x; 相當於是 cout.operator<<(x); 的函式呼叫, 你可以用 debugger 來確定 (把中斷點設在 cout << x; 然後逐步執行到函式裡, 你就可以看到執行到哪一個函式)ostream 這個類別不是我們自己寫的, 我們不可能去增加它的成員函式, 但是我們可以藉由覆蓋 operator<<() 這個全域函式來讓我們感覺好像是擴充了 ostream 這個類別的功能, 我們希望可以這樣子使用: Complex x, y; x.setValue(3, 4); y.setValue(5, 6); cout << '(' << x << ") * (" << y << ')'; // 在螢幕上列印出 (3 + 4 i) * (5 + 6 i)函式的定義應該如下: ostream &operator<<(ostream &os, Complex &rhs) { ... return os; } 新增上述程式之後, 你可以編譯一下, 然後立刻就發現了一個大問題, 編譯器不允許你在 operator<<() 函式中直接存取 Complex 類別內的私有資料成員, 這是適當封裝過的類別應該有的特性的特性: 所有的資料成員只有這個類別的成員函式才能夠直接存取, 要克服這個問題, 一般編譯器允許你用四種方法:
完成上面的函式之後, 你也可以將一個複數類別的物件以指定格式寫到檔案裡面,例如: Complex x; x.setValue(3, 4); ofstream file("outputfile.txt"); file << x; // 在檔案裡寫入 3 + 4 i請測試一下, 程式碼要保留 demo 給助教看 請注意上面的應用完全不需要另外增加別的設計, 這是 C++ 中繼承性質所帶來的好處, 請注意我們前面把 Complex::print() 改為 Complex::print(ostream &) 也是為了讓 print 能夠支援任意型態的資料串流, 我們在課程後面一點會再詳細地說明。 請注意: cout << "Hello"; 和 cout.operator<<("Hello"); 所呼叫的函式是不一樣的, 所以它們的表現是不一樣的, 你可以用 debugger 來驗證這件事, 事實上 cout << "Hello"; 是呼叫 operator<<(cout, "Hello") 函式來完成的, 而且根本就不存在 ostream::operator<<(char *) 這樣的成員函式, 同樣地 cout << 'h'; 是呼叫 operator<<(cout, 'h');, 根本不存在 ostream::operator<<(char) 這樣的成員函式, 如果你嘗試用 cout.operator<<('h'); 呼叫, 程式會執行, 但是你會發現其實呼叫到的成員函式是 ostream::operator<<(int)。 接下來你也可以嘗試擴充 iostream 中的 extraction operator 函式 istream &operator>>(istream &is, Complex &rhs) { ... return is; }
|
步驟六 | 在這個步驟中希望大家更進一步了解和運算子 << 等效的函式呼叫敘述,
首先大家知道
int x, y; cout << x; cout << x << y;可以寫成等效的 cout.operator<<(x); cout.operator<<(x).operator<<(y);或是 (cout.operator<<(x)).operator<<(y);請測試一下 在上面的敘述裡 cout.operator<<(x) 是呼叫一個名稱是 operator<< 的成員函式, 把 x 用參考的型態傳進函式裡, 函式會傳回一個 ostream& 型態的參考, 如上一步驟所示, 事實上是傳回 cout 物件的參考, 所以可以繼續作下一次的呼叫 (...).operator<<(y); 在上一個步驟裡我們定義了 ostream& operator<<(ostream&, Complex &) 的全域函式, 所以可以用下面的敘述: Complex x, y; x.setValue(3, 4); y.setValue(5, 6); cout << '(' << x << ")*(" << y << ')' << endl;請用 operator<<() 的語法改寫上面這一列程式 提示: endl 應該用 cout.operator<<(...), 字元和字串應該用 operator<<(cout, ...), 而我們自己定義的複數類別也應該用 operator<<(cout, ...), 請思考為什麼有的時候用 cout.operator<<(...) 有的時候用 operator<<(cout, ...)? |
步驟七 | 在這個步驟中, 希望大家能夠更清楚地知道為什麼要有參考型態的參數傳遞,
請用指標變數來取代參考變數, 定義下面的全域函式
ostream *operator<<(ostream *os, Complex src) { ... // 請列印 [[a + b i]] 以便和另外一個函式有所區別 return os; }請完成上面的函式的定義, 並且寫一小段和 Complex x; cout << x;等效但是運用上面的函式的敘述來測試, (Hint: 應該是 &cout << x;), 然後再寫一個和 Complex x; cout << x << endl;等效的敘述來測試, 程式請保留起來, demo 給助教看, 然後再寫和下列等效的敘述 cout << x << y << endl;以及和 operator<<(operator<<(cout, x), y).operator<<(endl); 等效的敘述來測試, 程式請保留起來, demo 給助教看, 做了這麼些練習後, 應該發覺 參考變數 (reference) 有很多好處了吧!! Hint: operator<<(operator<<(&cout, x), y)->operator<<endl); |
步驟八 | 請助教檢查後, 將所完成的 專案 (只需保留 .cpp, .h, .sln 以及 .vcxproj 檔案即可; 刪除掉 .suo, .sdf, .filters, .users, debug\ 資料匣, 以及 ipch\ 資料匣下的所有內容) 壓縮起來, 選擇 Lab4-2 上傳, 後面的實習課程需要使用這裡所完成的程式 |
回
C++ 物件導向程式設計課程 首頁 製作日期: 03/13/2013 by 丁培毅 (Pei-yih Ting) E-mail: pyting@mail.ntou.edu.tw TEL: 02 24622192x6615 海洋大學 電機資訊學院 資訊工程學系 Lagoon |