1 / 95

運算子重載

21. 運算子重載. Follow me, and I will make you fishers of men. 《Matthew (4 : 19)》 當建立自己的類別時,可以使用運算子重載 (operator overloading) , 建立自己的運算符號系統。. 運算子重載. 21.1  運算子使用的基本概念 21.2  補充幾個類別使用上的要點 21.3  使用成員函數重載二元運算子 21.4  使用 friend 函數重載二元運算子 21.5  重載一元運算子 21.6  含有指標資料成員的類別 21.7  等效阻抗的計算.

Download Presentation

運算子重載

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 21 運算子重載 Follow me, and I will make you fishers of men. 《Matthew (4:19)》 當建立自己的類別時,可以使用運算子重載 (operator overloading),建立自己的運算符號系統。

  2. 運算子重載 • 21.1 運算子使用的基本概念 • 21.2 補充幾個類別使用上的要點 • 21.3 使用成員函數重載二元運算子 • 21.4 使用friend函數重載二元運算子 • 21.5 重載一元運算子 • 21.6 含有指標資料成員的類別 • 21.7 等效阻抗的計算

  3. 21.1 運算子使用的基本概念 • 為自定的類別定義合用的運算符號常能簡化敘述,使程式的撰寫更簡潔,不易犯錯。例如 Vector A, B; float Answer = A*B; 而不用借助諸如下列的語法: float Answer = DotProduct(A, B);

  4. 二元的(binary)與一元的(unary) • 運算元 (operand): 運算子作用的對象。 • 二元運算子 (binary operator):每次都需要兩個運算元才能完成處理的運算子。例如:x*y • 二元運算子不可接連放置,例如 x*/y; // 錯誤的語法 • 一元運算子 (unary operator): 只和一個運算元作用的運算子。例如: -x

  5. 運算子的優先權(precedence of operators) • 當運算子不只一個時,常以小括號標明處理的先後。例如 (x+y)/(a+b) • 算術式 a*-b 則由於一元運算子「-」的優先權高於二元運算,它的處理程序相當於「a*(-b)」。

  6. 運算子的結合關係(associatively of operators) • 例如,二元運算子「*」和「%」的結合關係都是「和左邊的運算元結合」而且有相同的優先權。因此,算術式「a*b%c」的執行結果相當於 「(a*b)%c」。 • 並不是所有的符號都可以重載。 • 運算子() 有可能和左邊的運算元結合,譬如函數f(x) 和int(x),也有可能和右邊的運算元結合,例如(int)x,但是兩者具有不同的優先權。此外,「*」和「&」也有類似的情況。在這些場合,由於結合的關係不同,優先權也不同,必需視為不同的運算子。

  7. 可以重載的運算子及其特性(1/3)

  8. 可以重載的運算子及其特性(2/3)

  9. 可以重載的運算子及其特性(3/3)

  10. 運算子重載的限制 1.基本資料型態的運算子不可重載。 2.運算子的「優先權」、「結合關係」和「形式」都不可改變。 3.不可創造新的符號或結合兩個既有符號以定義成新的符號。 4.不在表內的符號不可使用。例如:「.」,「::」,「.*」,以及三元運算子「?:」都不能被重載。 5.運算子[ ] 最多只能接受一個參數,而運算子 ( ) 則對參數的數目沒有限制。

  11. 運算子重載的做法 • 運算子重載有兩種做法: • 1. 使用成員函數 (member function)。 • 2. 使用friend函數。 • 但兩種做法又依運算子是二元或一元的形式而有所不同。

  12. 自定類別MyClass • 類別MyClass的宣告如下: class MyClass { friend void F4(MyClass &); private: int Size; public: MyClass (int N): Size(N) {} ~MyClass() {} void F1(MyClass &); MyClass F2(); void F3(); } • 使用類別定義物件時,每個物件都擁有自己的資料成員,而同一類別的物件共用該類別的成員函數。

  13. 預設的成員函數(default member functions) • 編譯器 (compiler) 會自動加入四個成員函數: 1. 建構函數 (constructor) 2. 解構函數 (destructor) 3. 複製建構函數 (copy constructor) 4. 指派運算子 (assignment operator)

  14. 解構函數的自動呼叫 • 在區塊內定義的局部變數在離開其作用範圍 (scope) 時,就會自動呼叫。例如:

  15. 自定解構函數 • 解構函數是提供給程式設計者用來回收記憶資源的一種特殊語法。 • 如果類別的所有的資料成員都是屬於預設的資料型態,則不需要另外自行定義解構函數。 • 當類別中存在指標或字串、陣列這類衍生資料型態時,需要另外自行定義解構函數。

  16. 複製建構函數(copy constructor) • 使用已經存在的物件來建立新物件。例如: MyClass A(B); // 使用物件 B 來建立物件 A MyClass C = B;// 使用物件 B 來建立物件 C • 複製建構函數常以符號 X(X&) 來代表。在這個代表式裏X是假想的類別名稱,其參數使用同一個類別的參照 (reference)。

  17. 指派運算子(assignment operator) • 事實上就是我們熟悉的等號。例如: D = E; 就是標準的指派 (assignment) 運算。 • 如果資料成員內有指標時,這種單純的複製動作會發生問題。

  18. 隱藏的成員:指標this (1/2) • 使用public成員函數,例如: A.F3(); • 如果我們把建立A和F3()聯繫的動作明白地表示出來,則F3( ) 的原型可以寫成: void F3(MyClass* this);

  19. 隱藏的成員:指標this (2/2) • 這個this指標指向物件本身 (「this」是C++ 的關鍵字): 圖21.2.1this 指標 • 在本章的成員函數定義裏面,有一些處理程序必需用到這個this指標。

  20. 需要特別注意的Friend函數語法 1.它的宣告和public,private或protected無關,可以放在類別宣告內的任何地方。 2.「friend」這個關鍵字只需放在類別宣告的部份。 3.Friend函數的參數列中,至少要有一個該類別的物件,或該類別物件的參照 (reference)。 4.預設的四個成員函數都不可轉換為friend函數。

  21. 使用const參照的參數傳遞 • 傳參照 (pass-by-reference)時如果想要進一步避免原來的物件被修改,可以在參數列中使用const參照。例如: void F1(const MyClass&); • 使用const參照能兼顧效率和安全性。 • 複製建構函數 (copy constructor) X(X&) 也應該跟著修改成 X(const X&)。

  22. 定義新的類別Float • 以重新定義預設資料型態float對於「次方運算」的運算子。 • 在這個過程裏,我們可以仔細討論運算子重載的幾種基本語法。

  23. 和左側運算元結合的二元運算子 • 除了具有「指派」(assignment) 功能的運算符號,例如 =,*=,/=,<<=,>>=,&=,%=,-=,+=,^=,|= 之外,所有的二元運算子都和它左邊的運算元結合。 • 定義和左邊的運算元結合的二元運算子的成員函數時,只需要用到一個參數來代表符號右側的運算元。 • 省略的參數就是結合這個成員函數的物件本身。

  24. 重載二元運算子「+」和「^」 • 假設要定義的類別叫做Float,它有一個private資料成員 F: Float operator + (const Float& F2) const {return Float(F + F2.F); } Float operator ^ (const float& f) const {return Float(pow(F,f));} • 經由上式我們定義了兩個成員函數: operator +() operator ^() • 我們也可以使用「this」指標, 將 F + F2.F 寫成 this->F + F2.F 將 pow(F, f) 寫成 pow(this->F,f)

  25. 返回值最佳化 • operator +() 和operator ^() 的返回值 (return value)都是物件。例如,兩個成員函數的返回敘述 return Float(F+F2.F); return Float(pow(F,f)); 都呼叫了建構函數Float(float),以建立一個臨時的物件來容納計算結果。 • 這種在返回敘述內建立臨時的返回物件的做法,因為可以改善效率,稱為「返回值最佳化」 (return value optimization)。 • 並不是所有的成員函數都適用,只有處理程序單純的場合才用得著。

  26. 指派運算子「+=」的重載 • 指派運算子 (assignment operator)都和它右側的運算元結合。 • 以成員函數重載指派運算子時,需要右側的運算元做為其參數。 • 運算結果將改變運算子左側的運算元。例如: A += B; • 這個敘述將送回一個改變後的A,亦即 *this: Float& operator +=(const Float& F2) { F += F2.F; return *this; }

  27. 轉換運算子(conversion operator) • 將自定的物件轉換為C++ 內設的基本資料型態。例如: Float::operator float() 就是以內設的資料型態「float」做為轉換運算子的名稱。 • 轉換運算子只能以成員函數的形式定義。 • 轉換運算子在不同資料型態間進行「指派運算」時就會自動被呼叫。例如: float a; Float b(3.5); a = b; // 自動呼叫轉換運算子

  28. 成員函數operator float() 的定義 operator float() {return F;} • 注意,operator float() 的定義式不能再寫上返回資料型態: float operator float() {return F;} //錯誤!

  29. 標頭檔Float.h和主程式TestF.cpp • 包括類別Float的宣告和定義,使用成員函數重載運算子「+」,「*」,「^」和「+=」。 • 定義了轉換運算子float。 • 主程式TestF.cpp則是用來測試類別Float的正確性。 • 的確可以將運算符號「^」重新定義為次方運算,但代價是必需重新定義所有需要用到的運算子。

  30. 範例程式 檔案 Float.h (1/2) // Float.h #ifndef FLOAT_H #define FLOAT_H #include <iostream> using namespace std; class Float { private: float F; public: Float(float x) : F(x) {} Float(): F(0) {} // 定義 operator+() Float operator + (const Float& F2) const {return Float(F + F2.F); } // 定義 operator*() Float operator * (const Float& F2) const

  31. 範例程式 檔案 Float.h (2/2) {return Float(F * F2.F); } // 定義 operator^() Float operator ^ (const float& f) const {return Float(pow(F,f));} // 定義 轉換運算子 float operator float() {return F;} // 定義 operator += () Float& operator += (const Float& F2) { F += F2.F; return *this; } }; #endif

  32. 範例程式 檔案TestF.cpp // TestF.cpp #include "Float.h" int main() { Float f1(2.4), f2(3.5); cout << "f1 : " << f1 << endl; cout << "f2 : " << f2 << endl; cout << "f1+f2 : " << f1+f2 << endl; cout << "f1*f2 : " << f1*f2 << endl; cout << "f1^3.2: " << (f1^3.2) << endl; cout << "f1+=f2: " << (f1+=f2) << endl; return 0; }

  33. 程式執行結果

  34. 21.4 使用friend函數重載二元運算子 • 運算子左右兩側的運算元都要列出當作參數。 • 除了運算子「+=」以外,「+」,「*」,「^」 三個運算子都不應該更動它左右兩側的運算元,因此這些運算子的參數列都是使用const參照。 • 這三個friend函數的原型如下所示: friend Float operator + (const Float& Left, const Float& Right); friend Float operator * (const Float& Left, const Float& Right); friend Float operator ^ (const Float& Left, const Float& Right);

  35. 三個friend函數的定義 Float operator+ (const Float& Left, const Float& Right) {return Float(Left.F + Right.F);} Float operator* (const Float& Left, const Float& Right) {return Float(Left.F * Right.F);} Float operator^ (const Float& Left, const Float& Right) {return Float(pow(Left.F, Right.F));} • 這些定義都符合上節「返回值最佳化」的原則。

  36. operator +=() 的定義 • 代表運算子左側物件的參數必需使用參照 (reference),不能是const參照。 • 其返回值也應該是參照,才不會再另行創造一個物件。 • 這個friend函數的原型如下所示: friend Float & operator += (Float& Left, const Float& Right); • 函數定義: Float& operator += (Float& Left, const Float& Right) { Left.F += Right.F; return Left; }

  37. 標頭檔Float.h和主程式TestF.cpp • 標頭檔FloatNM.h包括類別Float的宣告和定義,使用friend函數重載運算子「+」,「*」,「^」和「+=」。 • 主程式TestF2.cpp用來測試FloatNM.h的正確性。

  38. 範例程式 檔案 FloatNM.h (1/3) // FloatNM.h #ifndef FLOATNM_H #define FLOATNM_H #include <iomanip> using namespace std; // ---- 類別Float的宣告 ----------------------------- class Float { friend Float operator + (const Float& Left, const Float& Right); friend Float operator * (const Float& Left, const Float& Right); friend Float operator ^ (const Float& Left, const Float& Right); friend Float& operator += (Float& Left, const Float& Right);

  39. 範例程式 檔案 FloatNM.h (2/3) private: float F; public: Float(float x) : F(x) {} Float(): F(0) {} // -- 定義 資料型態轉換運算子 float ------------- operator float() {return F;} }; // -- 定義 operator+() ------------------------ Float operator + (const Float& Left, const Float& Right) {return Float(Left.F + Right.F);}

  40. 範例程式 檔案 FloatNM.h (3/3) // -- 定義 operator*() ------------------------- Float operator * (const Float& Left, const Float& Right) {return Float(Left.F * Right.F);} // -- 定義 operator^() ------------------------- Float operator ^ (const Float& Left, const Float& Right) { return Float(pow(Left.F, Right.F));} // -- 定義 operator+=() ------------------------ Float&operator += (Float& Left, const Float& Right) { Left.F += Right.F; return Left;} #endif

  41. 範例程式 檔案 TestF2.cpp // TestF2.cpp #include "FloatNM.h" int main() { Float f1(2.4), f2(3.5); cout << "f1 : " << f1 << endl; cout << "f2 : " << f2 << endl; cout << "f1+f2 : " << f1+f2 << endl; cout << "f1*f2 : " << f1*f2 << endl; cout << "f1^3.2: " << (f1^3.2) << endl; cout << "f1+=f2: " << (f1+=f2) << endl; return 0; }

  42. 程式執行結果

  43. 21.5 重載一元運算子 • 前置,prefix: 運算子在運算元之前,所需要的參數數目比二元運算子少一個。 • 後置,postfix:運算子在運算元之後 (與左側的運算元結合),參數的數目和二元運算子相同。列表如下:

  44. 使用成員函數重載前置一元運算子 • 重載一元運算子負號和前置累加,亦即operator -() 和operator ++() 的語法: Float operator -() const // 定義 Prefix - { return Float(-F); } Float& operator ++() // 定義 Prefix ++ { ++F; return *this; } • operator -() 的運算並不改變原有物件,因此宣告為const成員函數。 • 前置運算子operator ++()則會改變原有物件,因此雖然參數列是空的,其傳回值是 *this,且返回的資料型態為參照「Float &」。

  45. 使用成員函數重載後置一元運算子 • 後置一元運算子operator ++() 使用成員函數定義的語法: Float operator ++(int) // 定義 Postfix ++ { Float Before(F); F++; return Before; } 在參數列中有一個int的額外資料型態,稱為啞常數 (dummy constant value)。 • 這個運算要先將原有值返回,之後才將原來物件的值改變。使用friend函數重載前置一元運算子

  46. 使用friend函數重載前置一元運算子 • 參與的運算元必需明白的寫出來,並在函數本體內使用「物件名稱 + 成員運算子(.) + 資料成員」的語法。 • 前置運算子operator !()定義為將內值平方,而前置運算子operator --() 則為累減。原型如下: friend Float operator ! (const Float& F1); friend Float& operator --(Float& F1); • 定義如下: Float operator ! (const Float& F1) { return Float(F1.F*F1.F); } Float& operator -- (Float& F1) { F1.F--; return F1; }

  47. 使用friend函數重載前置一元運算子 • 後置一元運算子operator --() 的宣告: friend Float operator -- (Float& F1, int); • 後置運算子operator --() 的定義: Float operator --(Float& F1, int) { Float Before(F1.F); F1.F--; return Before; }

  48. 檔案UnaryF.h 和TestUnary.cpp • 所有關於一元運算子重載的語法。在這個檔案裹面,我們對於各種一元運算子都做了全面性的示範,如表21.5.2所示。 表21.5.2 檔案UnaryF.h內所定義的一元運算子 • TestUnary.cpp是用來測試檔案UnaryF.h。

  49. 範例程式 檔案 UnaryF.h // UnaryF.h #ifndef UNARYF_H #define UNARYF_H #include <iostream> using namespace std; class Float { // 使用 friend 函數宣告 prefix operator! () friend Float operator ! (const Float& F1); // 使用 friend 函數宣告 Prefix operator--() friend Float& operator --(Float& F1); // 使用 friend 函數宣告 Postfix operator--() friend Float operator --(Float& F1, int); private: float F; public: // --- 定義 Float 的建構函數 ------------------------ Float(float x) : F(x) {} Float(): F(0) {}

  50. 範例程式 檔案 UnaryF.h // -- 定義 轉換運算子 float ------------------------- operator float() {return F;} // -- 使用成員函數定義 prefix operator - () Float operator -() const {return Float(-F);} // -- 使用成員函數定義 Prefix operator ++ () Float& operator++() { ++F; return *this; } // -- 定義 postfix operator ++ () Float operator++(int) { Float Before(F); F++; return Before; } };

More Related