1. 運算符重載簡介
所謂重載,就是賦予新的含義。函數重載(Function Overloading)可以讓一個函數名有多種功能,在不同情況下進行不同的操作。同樣運算符重載(Operator Overloading)可以讓同一個運算符可以有不同的功能。
- + 可以對 int、float、string 等不同類型數據進行操作
- << 既是位移運算符,又可以配合 cout 向控制台輸出數據
也可以自定義運算符重載:
class Complex
{
public:
Complex();
Complex(double real, double imag);
Complex operator+(const Complex &a) const;
void display() const;
private:
double m_real;
double m_imag;
};
// ...
// 實現運算符重載
Complex Complex::operator+(const Complex &A) const{
Complex B;
B.m_real = this->m_real + A.m_real;
B.m_imag = this -> m_imag + A.m_imag;
return B;
// return Complex(this->m_real + A.m_real, this->m_imag + A.m_imag);
}
int main(){
Complex c1(4.3, 5.8);
Complex c2(2.7, 3.7);
Complex c3;
c3 = c1 + c2; // 運算符重載
c3.display();
return 0;
}
運算結果
7 + 9.5i
運算符重載其實就是定義一個函數,在函數體內實現想要的功能,當用到該運算符時,編譯器會自動調用這個函數,它本質上是函數重載。
c3 = c1 + c2;
實際上通過調用成員函數 operator+(),會轉換為下面的形式:
c3 = c1.operator+(c2);
全局范圍內重載運算符
運算符重載函數不僅可以作為類的成員函數,還可以作為全局函數。
復數加法運算通過全局范圍內重載 + 實現:
class Complex{
// ...
friend complex operator+(const complex &A, const complex &B);
};
// 全局函數
Complex operator+(const Complex &A, const Complex &B)
{
return Complex(A.m_real + B.m_real, A.m_imag + B.m_imag); // 訪問了Complex 的 private 成員變量,需要聲明為友元函數
}
2. 運算符重載時要遵循的規則
-
能夠重載的運算符
+ - * / % ^ & | ~ ! = < > += = = /= %= ^= &= |= << >> <<= >>= == != <= >= && || ++ -- , -> - > () [] new new[] delete delete[]
長度運算符 sizeof、條件運算符: ?、成員選擇符. 和域解析運算符::不能被重載。
-
重載不能改變運算符的優先級和結合律
-
重載不會改變運算符的用法,即操作數個數、位置都不會改變
-
運算符重載函數不能有默認的參數,因為這改變了運算符操作數個數
-
運算符重載函數既可作為類成員函數,也可為全局函數,注意全局函數如何要訪問類對象的私有成員,需要聲明為類的友元
-
箭頭運算符->、下標運算符[]、函數調用運算符()、賦值運算符 =,只能以成員函數的形式重載。
3. 重載運算符實現形式的選擇
重載運算符可以通過成員函數和全局函數(友元)來實現
轉換構造函數
Complex c1(25, 25);
Complex c2 = c1 + 15.6;
Complex c3 = 28.23 + c1; // 要以全局函數實現重載
這幾行代碼都可以順利運行,說明 Complex 對象可以和 double 類型對象相加。其實,編譯器在檢測到 Complex 和 double(小數默認為 double 類型)相加時,會先嘗試將 double 轉換為 Complex,或者反過來將 Complex 轉換為 double(只有類型相同的數據才能進行 + 運算),如果都轉換失
敗,或者都轉換成功(產生了二義性),才報錯。實際上,上述兩行加法代碼會轉換為:
Complex c2 = operator+(c1,Complex(15.6));
Complex c3 = operator+(Complex(28.23),c1);
Complex(double real)
在作為普通構造函數的同時,還能將 double 類型轉換為 Complex 類型,集合了“構造函數”和“類型轉換”的功能,所以被稱為「轉換構造函數」。換句話說,轉換構造函數用來將其它類型(可以是 bool、int、double等基本類型,也可以是數組、指針、結構體、類等構造類型)轉換為當前類類型。
以全局函數形式重載
以全局函數的形式重載了 +、-、*、/、==、!=,這樣做是為了保證這些運算符能夠被對稱的處理。
如果將 operator+定義為成員函數,根據“+ 運算符具有左結合性”這條原則,Complex c2 = c1 + 15.6;
會被轉換為下面的形式:
Complex c2 = c1.operator+(Complex(15.6));
但是對於 Complex c3 = 28.23 + c1
,編譯器會嘗試轉換為不同形式:
Complex c3 = (28.23).operator+(c1);
顯然這是錯誤的。
以成員函數形式重載
以成員函數的形式重載了 +=、-=、 *=、/=。
運算符重載的初衷是給類添加新的功能,方便類的運算,它作為類的成員函數是理所應當的, 是首選的。不過類的成員函數不能對稱處理數據,運算符的第一個運算對象不會出現類型轉換(要類的對象才能調用類的成員函數)。
C++ 規定,箭頭運算符->、下標運算符[ ]、函數調用運算符( )、賦值運算符=只能以成員函數的形式重載。
4. 重載 >> 和 <<(輸入輸出運算符)詳解
C++ 標准庫已對 左移運算符 << 和 >> 右移運算符進行了承載,如果我們定義新的類型需要輸入輸出運算符去處理,需要再進行重載。
重載運算符 >>
以全局函數的形式重載>>,使它能夠讀入兩個 double 類型的數據,並分別賦值給復數的實部和虛部:
istream & operator>>(istream &in, Complex &A) // 友元
{
in >> A.m_real >> A.m_image;
return in;
}
之所以返回 istream 類 對象的引用,是為了能夠連續讀取復數,讓代碼書寫更加漂亮,例如:
Complex c1,c2;
cin >> c1 >> c2;
如果不返回引用,需要一個一個讀取:
cin >> c1;
cin >> c2;
實際上,上述 >> 運算符會被轉換成如下形式:
operator<<(cin, c);
重載運算符 <<
ostream & operator<<(ostream &out, complex &A){
out << A.m_real <<" + "<< A.m_imag <<" i ";
}
5. 重載 [] 下標運算符
下標運算符 [] 必須以成員函數的形式進行重載。
int& Array::operator[](int i){
return m_p[i];
}
const int & Array::operator[](int i) const{
return m_p[i];
}
當 Array 是 const 對象時,如果沒有提供 const 版本的 operator[],會報錯。
6. 重載 ++ 和 -- 自增自減運算符
class Stopwatch{
// ... 秒表類
public:
Stopwatch operator++(); // ++i,前置形式
Stopwatch operator++(int); // i++,后置形式
Stopwatch run(); // 運行
private:
int m_min; // 分鍾
int m_sec; // 秒鍾
};
Stopwatch Stopwatch::run(){
++m_sec;
if(m_sec == 60){
m_min++;
m_sec = 0;
}
return *this;
}
Stopwatch Stopwatch::operator++(){
return run();
}
Stopwatch Stopwatch::operator++(int n){
Stopwatch s = *this; // i++, 先返回對象,再對象自增,所以需要將對象保存
run();
return s;
}
函數中參數 n 是沒有任何意義的,它的存在只是為了區分是前置形式還是后置形式。
7. 重載 new 和 delete 運算符
內存管理運算符 new、new[]、delete 和 delete[] 也可以進行重載,其重載形式既可以是類的成員函數,也可以是全局函數。一般情況下,內建的內存管理運算符就夠用了,只有在需要自己管理內存時才會重載。
// 成員函數
void * className::operator new( size_t size ){
//TODO:
}
void className::operator delete( void *ptr){
//TODO:
}
// 全局函數
void * operator new( size_t size ){
//TODO:
}
void operator delete( void *ptr){
//TODO:
}
在重載 new 或 new[] 時,無論是作為成員函數還是作為全局函數,它的第一個參數必須是 size_t 類型。size_t 表示的是要分配空間的大小,對於 new[] 的重載函數而言,size_t 則表示所需要分配的所有空間的總和。
size_t 在頭文件
當然,重載函數也可以有其他參數,但都必須有默認值,並且第一個參數的類型必須是 size_t。
如果類中沒有定義 new 和 delete 的重載函數,那么會自動調用內建的 new 和 delete 運算符。
8. 重載()(強制類型轉換運算符)
類型強制轉換運算符是單目運算符,也可以被重載,但只能重載為成員函數,不能重載為全局函數。經過適當 重載后,(類型名)對象這個對對象進行強制類型轉換的表達式就等價於對象.operator 類型名(),即變成對運算符函數的調用。
class Complex{
public:
// ...
operator double()
{
return real;
}
private:
double m_real;
double m_imag;
};
int main()
{
Complex c(1.2,3.4);
cout << (double)c << endl; // 1.2
double n = 2 + c; // 等價於 double n = 2 + c.operator double()
}
9. 運算符重載總結
注意事項:
- 重載后運算符的含義應該符合原有用法習慣。例如重載 + 運算符,完成的功能就應該類似於做加法,在重載的+ 運算符中做減法是不合適的。此外,重載應盡量保留運算符原有的特性。
- C++ 規定,運算符重載不改變運算符的優先級。
- 以下運算符不能被重載:.、.*、::、? :、sizeof。
- 重載運算符()、[]、->、或者賦值運算符=時,只能將它們重載為成員函數,不能重載為全局函數。
-
運算符的實質是將運算符重載為一個函數,使用運算符的表達式就會被解釋為對重載函數的調用。
-
運算符可以重載為全局函數。此時函數的參數個數就是運算符的操作數個數,運算符的操作數就成為函數的實參。(友元)
-
運算符也可以重載為成員函數。此時函數的參數個數就是運算符的操作數個數減一,運算符的操作數有一個成為函數作用的對象,其余的成為函數的實參。
-
必要時需要重載賦值運算符=,以避免兩個對象內部的指針指向同一片存儲空間。
-
<<和>>是在 iostream 中被重載,才成為所謂的“流插入運算符”和“流提取運算符”的。
-
類型的名字可以作為強制類型轉換運算符,也可以被重載為類的成員函數。它能使得對象被自動轉換為某種類型。
-
自增、自減運算符各有兩種重載方式,用於區別前置用法和后置用法。
-
運算符重載不改變運算符的優先級。重載運算符時,應該盡量保留運算符原本的特性。