C++中預定義的運算子的操作物件只能是基本資料型別,實際上,對於很多使用者自定義型別,也需要有類似的運算操作。例如:
1 2 3 4 5 6 7 8 9 10 |
class complex { public: complex(double r=0.0,double I=0.0){real=r;imag=I;} void display(); private: double real; double imag; }; complex a(10,20),b(5,8); |
“a+b”運算如何實現?這時候我們需要自己編寫程式來說明“+”在作用於complex類物件時,該實現什麼樣的功能,這就是運算子過載。運算子過載是對已有的運算子賦予多重含義,使同一個運算子作用於不同型別的資料導致不同型別的行為。
運算子過載的實質是函式過載。在實現過程中,首先把指定的運算表示式轉化為對運算子函式的呼叫,運算物件轉化為運算子函式的實參,然後根據實參的型別來確定需要呼叫達標函式,這個過程愛編譯過程中完成。
一、 運算子過載的規則
運算子過載規則如下:
①、 C++中的運算子除了少數幾個之外,全部可以過載,而且只能過載C++中已有的運算子。
②、 過載之後運算子的優先順序和結合性都不會改變。
③、 運算子過載是針對新型別資料的實際需要,對原有運算子進行適當的改造。一般來說,過載的功能應當與原有功能相類似,不能改變原運算子的操作物件個數,同時至少要有一個操作物件是自定義型別。
不能過載的運算子只有五個,它們是:成員運算子“.”、指標運算子“*”、作用域運算子“::”、“sizeof”、條件運算子“?:”。運算子過載形式有兩種,過載為類的成員函式和過載為類的友元函式。
運算子過載為類的成員函式的一般語法形式為:
函式型別 operator 運算子(形參表)
{
函式體;
}
運算子過載為類的友元函式的一般語法形式為:
friend 函式型別 operator 運算子(形參表)
{
函式體;
}
其中,函式型別就是運算結果型別;operator是定義運算子過載函式的關鍵字;運算子是過載的運算子名稱。
當運算子過載為類的成員函式時,函式的引數個數比原來的操作個數要少一個;當過載為類的友元函式時,引數個數與原運算元個數相同。原因是過載為類的成員函式時,如果某個物件使用過載了的成員函式,自身的資料可以直接訪問,就不需要再放在參數列中進行傳遞,少了的運算元就是該物件本身。而過載為友元函式時,友元函式對某個物件的資料進行操作,就必須通過該物件的名稱來進行,因此使用到的引數都要進行傳遞,運算元的個數就不會有變化。
運算子過載的主要優點就是允許改變使用於系統內部的運算子的操作方式,以適應使用者自定義型別的類似運算。
二、 運算子過載為成員函式
對於雙目運算子B,如果要過載B為類的成員函式,使之能夠實現表示式oprd1 B oprd2,其中oprd1為類A的物件,則應當把B過載為A類的成員函式,該函式只有一個形參,形參的型別是oprd2所屬的型別。經過過載後,表示式oprd1 B oprd2 就相當於函式呼叫oprd1.operator B(oprd2).
對於前置單目運算子U,如“-”(負號)等,如果要過載U為類的成員函式,用來實現表示式U oprd,其中oprd為A類的物件,則U應當過載為A類的成員函式,函式沒有形參。經過過載之後,表示式U oprd相當於函式呼叫oprd.operator U().
對於後置運算子“++”和“- -”,如果要將它們過載為類的成員函式,用來實現表示式oprd++或oprd–,其中oprd為A類的物件,那麼運算子就應當過載為A類的成員函式,這時函式要帶有一個整型形參。過載之後,表示式oprd++和oprd—就想當於函式呼叫oprd.operator++(0)和oprd.operator—(0);
運算子過載就是賦予已有的運算子多重含義。通過重新定義運算子,使它能夠用於特定類的物件執行特定的功能,這便增強了C++語言的擴充能力。
1. 運算子過載的作用:
運算子過載允許C/C++的運算子在使用者定義型別(類)上擁有一個使用者定義的意義。過載的運算子是函式呼叫的語法修飾:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class Fred { public: // ... }; #if 0 // 沒有算符過載: Fred add(Fred, Fred); Fred mul(Fred, Fred); Fred f(Fred a, Fred b, Fred c) { return add(add(mul(a,b), mul(b,c)), mul(c,a)); // 哈哈,多可笑... } #else // 有算符過載: Fred operator+ (Fred, Fred); Fred operator* (Fred, Fred); Fred f(Fred a, Fred b, Fred c) { return a*b + b*c + c*a; } #endif |
2. 可以用作過載的運算子:
算術運算子:+,-,*,/,%,++,–;
位操作運算子:&,|,~,^,<<,>>
邏輯運算子:!,&&,||;
比較運算子:<,>,>=,<=,==,!=;
賦值運算子:=,+=,-=,*=,/=,%=,&=,|=,^=,<<=,>>=;
其他運算子:[],(),->,,(逗號運算子),new,delete,new[],delete[],->*。
下列運算子不允許過載:
.,.*,::,?:
3. 運算子過載後,優先順序和結合性:
使用者過載新定義運算子,不改變原運算子的優先順序和結合性。這就是說,對運算子過載不改變運算子的優先順序和結合性,並且運算子過載後,也不改變運算子的語法結構,即單目運算子只能過載為單目運算子,雙目運算子只能過載雙目運算子。
4. 編譯程式如何選用哪一個運算子函式:
運算子過載實際是一個函式,所以運算子的過載實際上是函式的過載。編譯程式對運算子過載的選擇,遵循著函式過載的選擇原則。當遇到不很明顯的運算時,編譯程式將去尋找引數相匹配的運算子函式。
5. 過載運算子有哪些限制:
(1) 不可臆造新的運算子。必須把過載運算子限制在C++語言中已有的運算子範圍內的允許過載的運算子之中。
(2) 過載運算子堅持4個“不能改變”。
·不能改變運算子運算元的個數;
·不能改變運算子原有的優先順序;
·不能改變運算子原有的結合性;
·不能改變運算子原有的語法結構。
6. 運算子過載時必須遵循哪些原則:
運算子過載可以使程式更加簡潔,使表示式更加直觀,增加可讀性。但是,運算子過載使用不宜過多,否則會帶來一定的麻煩。
(1) 過載運算子含義必須清楚。
(2) 過載運算子不能有二義性。
運算子過載函式的兩種形式
運算子過載的函式一般地採用如下兩種形式:成員函式形式和友元函式形式。這兩種形式都可訪問類中的私有成員。
1. 過載為類的成員函式
這裡先舉一個關於給複數運算過載複數的四則運算子的例子。複數由實部和虛部構造,可以定義一個複數類,然後再在類中過載複數四則運算的運算子。先看以下原始碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
#include <iostream.h> class complex { public: complex() { real=imag=0; } complex(double r, double i) { real = r, imag = i; } complex operator +(const complex &c); complex operator -(const complex &c); complex operator *(const complex &c); complex operator /(const complex &c); friend void print(const complex &c); private: double real, imag; }; inline complex complex::operator +(const complex &c) { return complex(real + c.real, imag + c.imag); } inline complex complex::operator -(const complex &c) { return complex(real - c.real, imag - c.imag); } inline complex complex::operator *(const complex &c) { return complex(real * c.real - imag * c.imag, real * c.imag + imag * c.real); } inline complex complex::operator /(const complex &c) { return complex((real * c.real + imag + c.imag) / (c.real * c.real + c.imag * c.imag), (imag * c.real - real * c.imag) / (c.real * c.real + c.imag * c.imag)); } void print(const complex &c) { if(c.imag<0) cout<<c.real<<c.imag<<'i'; else cout<<c.real<<'+'<<c.imag<<'i'; } void main() { complex c1(2.0, 3.0), c2(4.0, -2.0), c3; c3 = c1 + c2; cout<<"/nc1+c2="; print(c3); c3 = c1 - c2; cout<<"/nc1-c2="; print(c3); c3 = c1 * c2; cout<<"/nc1*c2="; print(c3); c3 = c1 / c2; cout<<"/nc1/c2="; print(c3); c3 = (c1+c2) * (c1-c2) * c2/c1; cout<<"/n(c1+c2)*(c1-c2)*c2/c1="; print(c3); cout<<endl; } |
該程式的執行結果為:
c1+c2=6+1i
c1-c2=-2+5i
c1*c2=14+8i
c1/c2=0.45+0.8i
(c1+c2)*(c1-c2)*c2/c1=9.61538+25.2308i
在程式中,類complex定義了4個成員函式作為運算子過載函式。將運算子過載函式說明為類的成員函式格式如下:
<類名> operator <運算子>(<參數列>)
其中,operator是定義運算子過載函式的關鍵字。
程式中出現的表示式:
c1+c2
編譯程式將給解釋為:
c1.operator+(c2)
其中,c1和c2是complex類的物件。operator+()是運算+的過載函式。
該運算子過載函式僅有一個引數c2。可見,當過載為成員函式時,雙目運算子僅有一個引數。對單目運算子,過載為成員函式時,不能再顯式說明引數。過載為成員函式時,總時隱含了一個引數,該引數是this指標。this指標是指向呼叫該成員函式物件的指標。
2. 過載為友元函式:
運算子過載函式還可以為友元函式。當過載友元函式時,將沒有隱含的引數this指標。這樣,對雙目運算子,友元函式有2個引數,對單目運算子,友元函式有一個引數。但是,有些執行符不能過載為友元函式,它們是:=,(),[]和–>。
過載為友元函式的運算子過載函式的定義格式如下:
friend <型別說明符> operator <運算子>(<參數列>)
{……}
下面用友元函式程式碼成員函式,過載編寫上述的例子,程式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
#include <iostream.h> class complex { public: complex() { real=imag=0; } complex(double r, double i) { real = r, imag = i; } friend complex operator +(const complex &c1, const complex &c2); friend complex operator -(const complex &c1, const complex &c2); friend complex operator *(const complex &c1, const complex &c2); friend complex operator /(const complex &c1, const complex &c2); friend void print(const complex &c); private: double real, imag; }; complex operator +(const complex &c1, const complex &c2) { return complex(c1.real + c2.real, c1.imag + c2.imag); } complex operator -(const complex &c1, const complex &c2) { return complex(c1.real - c2.real, c1.imag - c2.imag); } complex operator *(const complex &c1, const complex &c2) { return complex(c1.real * c2.real - c1.imag * c2.imag, c1.real * c2.imag + c1.imag * c2.real); } complex operator /(const complex &c1, const complex &c2) { return complex((c1.real * c2.real + c1.imag * c2.imag) / (c2.real * c2.real + c2.imag * c2.imag), (c1.imag * c2.real - c1.real * c2.imag) / (c2.real * c2.real + c2.imag * c2.imag)); } void print(const complex &c) { if(c.imag<0) cout<<c.real<<c.imag<<'i'; else cout<<c.real<<'+'<<c.imag<<'i'; } void main() { complex c1(2.0, 3.0), c2(4.0, -2.0), c3; c3 = c1 + c2; cout<<"/nc1+c2="; print(c3); c3 = c1 - c2; cout<<"/nc1-c2="; print(c3); c3 = c1 * c2; cout<<"/nc1*c2="; print(c3); c3 = c1 / c2; cout<<"/nc1/c2="; print(c3); c3 = (c1+c2) * (c1-c2) * c2/c1; cout<<"/n(c1+c2)*(c1-c2)*c2/c1="; print(c3); cout<<endl; } |
該程式的執行結果與上例相同。前面已講過,對又目運算子,過載為成員函式時,僅一個引數,另一個被隱含;過載為友元函式時,有兩個引數,沒有隱含引數。因此,程式中出現的 c1+c2
編譯程式解釋為:
operator+(c1, c2)
呼叫如下函式,進行求值,
complex operator +(const coplex &c1, const complex &c2)
3. 兩種過載形式的比較
一般說來,單目運算子最好被過載為成員;對雙目運算子最好被過載為友元函式,雙目運算子過載為友元函式比過載為成員函式更方便此,但是,有的雙目運算子還是過載為成員函式為好,例如,賦值運算子。因為,它如果被過載為友元函式,將會出現與賦值語義不一致的地方。 其他運算子的過載舉例
1).下標運算子過載
由於C語言的陣列中並沒有儲存其大小,因此,不能對陣列元素進行存取範圍的檢查,無法保證給陣列動態賦值不會越界。利用C++的類可以定義一種更安全、功能強的陣列型別。為此,為該類定義過載運算子[]。
下面一個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#include <iostream.h> class CharArray { public: CharArray(int l) { Length = l; Buff = new char[Length]; } ~CharArray() { delete Buff; } int GetLength() { return Length; } char & operator [](int i); private: int Length; char * Buff; }; char & CharArray::operator [](int i) { static char ch = 0; if(i<Length&&i>=0) return Buff[i]; else { cout<<"/nIndex out of range."; return ch; } } void main() { int cnt; CharArray string1(6); char * string2 = "string"; for(cnt=0; cnt<8; cnt++) string1[cnt] = string2[cnt]; cout<<"/n"; for(cnt=0; cnt<8; cnt++) cout<<string1[cnt]; cout<<"/n"; cout<<string1.GetLength()<<endl; } |
該陣列類的優點如下:
(1) 其大小不一定是一個常量。
(2) 執行時動態指定大小可以不用運算子new和delete。
(3) 當使用該類陣列作函式引數時,不心分別傳遞陣列變數本身及其大小,因為該物件中已經儲存大小。
在過載下標運算子函式時應該注意:
(1) 該函式只能帶一個引數,不可帶多個引數。
(2) 不得過載為友元函式,必須是非static類的成員函式。 2). 過載增1減1運算子.
增1減1運算子是單目運算子。它們又有字首和字尾運算兩種。為了區分這兩種運算,將字尾運算視為又目運算子。表示式
obj++或obj–
被看作為:
obj++0或obj–0
下面舉一例子說明過載增1減1運算子的應用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#include <iostream.h> class counter { public: counter() { v=0; } counter operator ++(); counter operator ++(int ); void print() { cout<<v<<endl; } private: unsigned v; }; counter counter::operator ++() { v++; return *this; } counter counter::operator ++(int) { counter t; t.v = v++; return t; } void main() { counter c; for(int i=0; i<8; i++) c++; c.print(); for(i=0; i<8; i++) ++c; c.print(); } |
3). 過載函式呼叫運算子
可以將函式呼叫運算子()看成是下標運算[]的擴充套件。函式呼叫運算子可以帶0個至多個引數。下面通過一個例項來熟悉函式呼叫運算子的過載。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream.h> class F { public: double operator ()(double x, double y) const; }; double F::operator ()(double x, double y) const { return (x+5)*y; } void main() { F f; cout<<f(1.5, 2.2)<<endl; } |