260 likes | 325 Views
2008/4/22 課後輔導 實作拷貝建構子與多載運算子. 課程內容. 本週實習程式講解 拷貝建構子 多載運算子 賦值運算子( = ) 串流插入運算子( << ). 實習程式講解. 本週實習程式. 請實作一個類別 CppString ,用以儲存字串並操作字串相關運算, private 成員資料中請以 char * 來實作此類別。 在類別中實作以下成員函數: 建構子、解構子與拷貝建構子 多載運算子「 operator== 」 多載運算子「 operator= 」 length() :回傳字串長度。 多載串流擷取運算子「 >> 」
E N D
2008/4/22 課後輔導實作拷貝建構子與多載運算子
課程內容 • 本週實習程式講解 • 拷貝建構子 • 多載運算子 • 賦值運算子(=) • 串流插入運算子(<<)
本週實習程式 • 請實作一個類別CppString,用以儲存字串並操作字串相關運算,private成員資料中請以char *來實作此類別。 • 在類別中實作以下成員函數: • 建構子、解構子與拷貝建構子 • 多載運算子「operator==」 • 多載運算子「operator=」 • length():回傳字串長度。 • 多載串流擷取運算子「>>」 • 多載串流插入運算子「<<」
練習一:建立類別 • 請建立類別設計的架構 • 成員變數: • 依題意,加入一個指向字元陣列的指標變數,另外,需要額外的變數以記住陣列的大小 • 成員函式 • 預設建構子 • 以輸入的字串作為初始值,欲設為空字串;例如: CppString(const char *s = “”); • 解構子 • 測試函式Output:用以列印成員變數,檢查資料是否正確 • 請撰寫簡單的main函式,編譯你的類別並測試物件的建立是否正確。 • 注意char*型態的字串處理需加入標頭檔<cstring>
Shallow Copy • 如果類別沒有給予拷貝建構子或多載賦值運算子(=),其預設的物件拷貝是將其成員內容逐一的複製。 • 範例: 物件B class BigObj { char buf[100]; int i; }; void main() { BigObj A, B; A = B; } char buf[100] int i 物件A
Shallow Copy • 如果當類別中有成員變數是指標變數的時候,考慮以下類別: class BigObj2 { public: BigObj2(int s=100) { capacity = (s > 0)? s : 100; buf = new char[capacity]; } ~BigObj2() { delete [] buf; } private: int capacity; char *buf; }; 在這個類別中有一個指標變數,指向一個字元的陣列;成員變數capacity用來記住目前陣列的大小。
Shallow Copy • 在預設的情況下,兩個指標變數的拷貝僅僅拷貝了記憶體位址,非針對實體的資料進行拷貝。 物件B Shallow Copy int capacity char *buf 30000 預設的拷貝動作,是對成員變數逐一的複製 30000 38000 38000 30000 物件A
Shallow Copy的問題 • 考慮以下程式碼將發生的情形: • func()接受一個物件做為參數,再將此物件回傳。 • main函式宣告兩個物件A與B,並將A傳入func後,以B物件來接受其回傳值。 BigObj2 func(BigObj2 X) { return X; } void main() { BigObj2 A, B; B = func(A); }
Shallow Copy的問題 main() func() main() 暫存的物件T Copy Copy Copy A X B 物件T operator= int capacity char *buf 30000 暫存的物件在函式結束呼叫便會被清除 30000 ? Dangling Pointer 38000 38000 30000 Memory Leak 物件B
自訂拷貝建構子與多載賦值運算子 • 使用時機 • 當成員變數含有指標變數的時候,最好提供自訂以下函式: • 預設建構子 • 不管什麼時機,都盡量提供一個預設建構子 • 自訂的拷貝建構子 • 多載賦值運算子(operator=)
自訂拷貝建構子 • 呼叫時機: • 從一個既存物件A中建立一個新的物件,且此物件為A的複本。 • 函式原型: • 沒有回傳值 • 一定只有一個參數,例: const BigObj2 & • 一定是一個const的參考指向傳入的物件;代表該傳入的物件不可被修改。
自訂拷貝建構子 class BigObj2 //因篇幅有限故直接寫入類別中,實作時請將函式原型 { //與實作分開 public: … BigObj2(const BigObj2 &source) { capacity = source.capacity; buf = new char[capacity]; for (int i=0; i<capacity; i++) buf[i] = source.buf[i]; } private: … }; 直接複製capacity 根據capacity,再次new一個新的陣列 逐一複製陣列中的元素
練習二:加入拷貝建構子 • 請於類別中加入拷貝建構子 • 請撰寫簡單的main函式,編譯你的類別並測試物件的建立是否正確。 • 例: CppString A(“hello”); CppString B = A; B.Output();
多載賦值運算子 • 呼叫時機: • 將一個既存物件B指定給另一個既存的物件A的時候 • 呼叫賦值運算子(operator=) BigObj A, B; A = B; //等同於呼叫A.operator=(B); • 與實作拷貝建構子的差別 • 因為賦值運算子是拷貝到一個「已經存在」的物件,表示原物件中已經有一些資料存在,故需增加一個程式碼來對舊有資料作清除,且需要判斷指定的物件是否就是自己本身。
多載賦值運算子 class BigObj2 { public: … BigObj2 &operator=(const BigObj2 &source) { if (this == &source) //判斷指定的物件是否就是自己本身 return *this; //例如: A = A; if (buf != NULL) //將自己所指向的記憶體釋回 delete [] buf; capacity = source.capacity; //以下同拷貝建構子的內容 buf = new char[capacity]; for (int i=0; i<capacity; i++) buf[i] = source.buf[i]; return *this; //將自己本身回傳 } private: … };
多載賦值運算子 • 介面說明;以類別BigObj為例: • 參數:const BigObj &source • 接受一個和自己同型態的物件參考,且指定const,代表傳入的物件不可修改。 • 注意傳入參考使得副函式具有更改到原物件的能力,加上const可保證傳入的物件不會被副函式任意更動。 • 通常賦值運算子會把自己(*this)回傳,回傳的型態是一個指向該物件的參考。這樣做的目的是為了實現「連鎖」的指定,例如: A = B = C; • 此行敘述等同於 A.operator=(B.operator=(C));
練習三:加入operator=() • 請於類別中多載賦值運算子 • 請撰寫簡單的main函式,編譯你的類別並測試物件的建立是否正確。 • 例: CppString A(“hello”); CppString B(“world”); B.Output(); B = A; B.Output();
多載串流插入運算子 • 例如: cout << classObject • 因為串流插入運算子的左邊是串流輸出物件,型態為ostream &,故不能把他以類別的成員函式的方法多載 • 因此必須以副函式的方式來多載。 • 同樣,在多載串流擷取運算子的時候也是一樣要用副函式的方式多載。
多載串流插入運算子 • 程式範例:以複數類別Complex為例 • 底層包含實部與虛部兩部分 • 我們希望利用>>運算子印出他的物件時,會自動印出以下這種形式: 3 + 5i
範例 class Complex { friend ostream &operator<< (ostream &out, Complex &P); public: … private: int real, imaginary; }; ostream &operator<<(ostream &out, Complex &P) { out << P.real << “+” << P.imaginary << “i”; return out; }
範例 • In main(): #include <iostream> using namespace std; int main() { Complex P, Q; … cout << P; cout << P << Q; return 0; } This statement is interpreted as: operator<<(cout, P); This statement is interpreted as: operator<< (operator<<(cout, P), Q);
練習四:多載串流插入運算子 • 請於類別中多載串流插入運算子 • 請撰寫簡單的main函式,編譯你的類別並測試物件的建立是否正確(把呼叫Output()的部分改用<<運算子的方式輸出)。 • 例: CppString A(“hello”); CppString B(“world”); cout << B << endl; B = A; cout << B << endl;