《隨筆二十二》—— C++中的“ 運算子過載 ”
目錄
前言
● 為什麼要對運算子進行過載:
C++預定義中的運算子的操作物件只侷限於基本的內建資料型別,但是對於我們自定義的型別是沒有辦法操作的。但是大多時候我們需要對我們定義的型別進行類似的運算,這個時候就需要我們對這麼運算子進行重新定義,賦予其新的功能,以滿足自身的需求。
● C++運算子過載的實質:
運算子過載的實質還是函式過載。C++中的每一個運算子對應著一個運算子函式, ,在實現過程中,把指定的運算表示式中的運算子轉化為對運算子函式的呼叫,而表示式中的運算物件轉化為運算子函式的實參,這個過程是在編譯階段完成的。
過載運算子的兩種形式
● 過載運算子有兩種方式,即:過載為類的成員函式 ,過載為類的非成員函式。對於每一種過載形式,由於運算子不同,都可以分為雙目運算子和單目運算子的實現。
過載為類的成員函式的時候:
將運算子過載為類的成員函式 , 稱為運算子成員函式。實際使用時, 總是通過該類的物件訪問過載的運算子。運算子成員函式在類內進行宣告, 在類外進行定義, 一般形式為:
返回型別 operator 運算子 (引數表);
在類外定義為:
返回型別 類名:: operator 運算子 (引數表) { // Statement }
雙目運算子過載為成員函式:
雙目運算子過載為成員函式時, 左運算元是訪問該過載運算子的物件本身的資料, 此時成員運算子函式只有一個引數。
雙目運算子過載為成員函式後, 就可以在主函式或其他類中進行呼叫了。在C++中,一般有顯式和隱式兩種呼叫方法:
顯式呼叫: <物件名>.operator <運算子>(<引數>)
隱式呼叫:<物件名><運算子><引數>
例如:a+b 等價於a.operator +(b)
單目運算子過載為成員函式:
單目運算子過載為成員函式時, 運算元是訪問該過載運算子物件本身的資料, 由this指標指出, 此時成員運算子函式沒有引數。
與雙目運算子的過載類似,單目運算子過載為成員函式後,在呼叫時也有顯式和隱式兩種:
顯式呼叫: <物件名>.operator <運算子>()
隱式呼叫:<物件名><運算子>
例如:++a等價於a.operator++()
過載為類的非成員函式的時候:
通常我們都將其宣告為友元函式,因為大多數時候過載運算子要訪問類的私有資料,(當然也可以設定為非友元非類的成員函式。但是非友元又不是類的成員函式是沒有辦法直接訪問類的私有資料的),如果不宣告為類的友元函式,而是通過在此函式中呼叫類的公有函式來訪問私有資料會降低效能。所以一般都會設定為類的友元函式,這樣我們就可以在此非成員函式中訪問類中的資料了。
友元運算子函式在類內宣告語法為:
friend 返回型別 operator 運算子 (引數表);
在類外定義為:
返回型別 operator 運算子 (引數表)
{
// Statement
}
雙目運算子過載為友元函式:
雙目運算子過載為友元函式時,由於沒有this指標,所以兩個運算元都要通過友元運算子函式的引數指出。雙目運算子過載為友元函式後,其呼叫有顯式和隱式兩種方法:
顯式呼叫: operator <運算子>(<實參1>,<實參2>)
隱式呼叫:<實參1><運算子><實參2>
注意: 友元運算子函式的形參 和和呼叫的實參 各方面要一一對應。
單目運算子過載為友元函式:
與單目運算子過載為成員函式不同, 單目運算子過載為友元函式時, 由於沒有this指標, 所以運算元要通過友元運算子函式的引數指出。單目運算子過載為友元函式後也有顯式和隱式兩種呼叫方法:
顯式呼叫: operator <運算子>(<實參1>)
隱式呼叫:<運算子><實參2>
注意; 在將運算子過載為友元函式時, 除運算子 =、( )、[]、-> 不能用友元函式過載外, 只能通過成員函式過載。其餘的運算子都可以進行過載。此外, 使用友元函式過載單目運算子 “++"和“-” 時,由於要改變運算元自身的值, 所以應採用引用引數傳遞運算元,否則會出現錯誤。
運算子成員函式 和 運算子友元函式的比較
在多數情況下,將運算子過載為類的成員函式和類的友元函式都是可以的。但成員函式運算子與友元函式運算子也具有各自的一些特點:
- 雙目運算子可被過載為友元函式,也可被過載為成員函式, 但是在運算子的左運算元是 一個標準資料型別,而右運算元是物件的情況下, 必須將它過載為友元函式,原因是標準資料型別的資料不能產生對過載運算子的呼叫。
- 對於雙目運算子 運算子成員函式是類的成員, 帶有this指標, 只需一個引數; 而友元運算子函式不是類的成員, 不帶this指標,引數必須是兩個。對於單目運算子, 成員運算子函式不帶引數; 而友元運算子必須帶一個引數。
- 一般而言, 對於雙目運算子過載為友元函式較好, 若運算子的運算元特別是左操 作數需要進行隱式型別轉換, 必須過載為友元運算子函式。若一個運算子需要修改物件的狀態, 則選擇運算子成員函式較好。、
- 當運算子函式是一個成員函式時,最左邊的運算元(或者只有最左邊的運算元)必須是運算子類的一個類物件(或者是對該類物件的引用)。如果左邊的運算元必須是一個不同類的物件,或者是一個內部型別的物件,該運算子函式必須作為一個友元函式來實現。
- 當需要過載運算子具有可交換性時,選擇過載為友元函式。
C++允許過載的運算子
- 雙目算術運算子 + (加),-(減),*(乘),/(除),% (取模)
- 關係運算符 ==(等於),!= (不等於),< (小於),> (大於>,<=(小於等於),>=(大於等於)
- 邏輯運算子 ||(邏輯或),&&(邏輯與),!(邏輯非)
- 單目運算子 + (正),-(負),*(指標),&(取地址)
- 自增自減運算子 ++(自增),--(自減)
- 位運算子 | (按位或),& (按位與),~(按位取反),^(按位異或),,<< (左移),>>(右移)
- 賦值運算子 =, +=, -=, *=, /= , % = , &=, |=, ^=, <<=, >>=
- 空間申請與釋放 new, delete, new[ ] , delete[]
- 其他運算子 ( ) (函式呼叫),-> (成員訪問),->*(成員指標訪問),,(逗號),[](下標)
- 輸入輸出運算子 << 、>>
運算子 | 建議使用 |
所有一元運算子 | 成員函式 |
= ( ) [ ] -> | 必須是成員函式 |
+= -= /= *= ^= &= != %= >>= <<= , 似乎帶等號的都在這裡了 | 成員函式 |
所有其它二元運算子, 例如: –,+,*,/ | 友元函式 |
<< >> | 必須是友元函式 |
不能過載的運算子只有5個:
成員訪問運算子 | . |
成員指標訪問運算子 | .* |
作用域運算子 | :: |
長度運算子 | sizeof |
條件運算子 | ?: |
引數和返回值
當引數不會被改變,一般按const引用來傳遞 (若是使用成員函式過載,函式也為const).
對於返回數值的決定:
- 如果返回值可能出現在=號左邊, 則只能作為左值, 返回非const引用。
- 如果返回值只能出現在=號右邊, 則只需作為右值, 返回const型引用或者const型值。
- 如果返回值既可能出現在=號左邊或者右邊, 則其返回值須作為左值, 返回非const引用。
下面看具體程式碼:看不懂,可以複製到編譯器上, 一個個的看。
#include <iostream>
using namespace std;
class Complex
{
public:
Complex() = default;
Complex(int r , int i )
{
m_real = r;
m_img = i;
}
~Complex()
{
delete arr;
arr = nullptr;
}
friend Complex operator+(Complex &m, Complex &s); //兩物件相加,友元
//還可以定義一個轉換建構函式,來實現 類物件和基本型別的計算
Complex operator+(Complex &m); //兩物件相加, 成員函式
friend Complex operator+(Complex &i, int b); //物件與整型數相加,友元
Complex operator+(const int b); //物件與整型數相加,成員函式
// 前置++ 成員和友元函式實現
Complex &operator++();
friend Complex &operator++(Complex &a);
// 後置++ 成員和友元函式實現
Complex operator++(int);
friend Complex operator++(Complex &a, int);
friend ostream &operator<<(ostream &output,const Complex &obj);
friend istream &operator>>(istream &input, Complex &obj);
int &operator[](int i); //下標運算子過載
Complex &operator=(const Complex &source);
void display()
{
cout << "輸出m_real:" << m_real << " 輸出m_img:" << m_img << "\n" << endl;
}
private:
int m_real;
int m_img;
int *arr = new int[3]{ 1,2,3};
};
int &Complex::operator[](int i) //下標運算子過載
{
if (i < 0 || i >= 3)
{
cout << "錯誤,下標越界!" << endl;
system("pause");
exit(1);
}
return arr[i];
}
ostream &operator<<(ostream &output, const Complex &obj)
{
output << "m_real:" << obj.m_real << " " << "m_img:" << obj.m_img << endl;
output << "輸出arr 陣列中的值:";
for (size_t i = 0; i != 3; ++i)
{
output << obj.arr[i] << " ";
}
cout << endl;
return output;
}
istream &operator>>(istream &input, Complex &obj)
{
cout << "請輸入m_real的值:";
input >> obj.m_real;
cout << "請輸入m_img的值:";
input >> obj.m_img;
delete obj.arr;
obj.arr = nullptr;
obj.arr = new int[3];
cout << "請分別輸入arr 陣列的元素,然後用回車符隔開:" << endl;
for (size_t i = 0; i != 3; ++i)
{
input >> obj.arr[i];
}
cout << endl;
return input;
}
Complex &Complex::operator=(const Complex &source)
{
if (this == &source) //自我賦值的檢查
{
return *this;
}
m_real = source.m_real;
m_img = source.m_img;
if (arr)
{
for (size_t i = 0; i != 3; ++i)
{
arr[i] = source.arr[i];
}
}
return *this;
}
Complex &Complex::operator++()// 成員函式實現 前置++
{
++m_img;
++m_real;
return *this;
}
Complex &operator++(Complex &a)
{
++a.m_img; ++a.m_real;
return a;
}
Complex Complex::operator++(int) // 成員函式實現 後置++
{
int i = m_real++;
int r = m_img++;
return Complex(i,r );
}
Complex operator++(Complex &a, int) // 友元函式實現 後置++
{
return Complex(a.m_real++, a.m_img++);
}
Complex operator+(Complex &m, Complex &s)
{
return Complex(m.m_real + s.m_real, m.m_img + s.m_img);
}
Complex Complex::operator+(Complex &m)
{
return Complex(m_real + m.m_real, m_img + m.m_img);
}
Complex operator+(Complex &i, int b) //物件與整型數相加,友元
{
return Complex(i.m_real + b, i.m_img + b);
}
Complex Complex::operator+(const int b) //物件與整型數相加,成員函式
{
return Complex(m_real + b, m_img + b);
}
int main()
{
Complex c1(2, 3);
Complex c2(5, 4);
Complex c3;
cout << "\n\n使用兩個Complex物件的加法\n" << endl;
c3 = operator+(c1, c2); //兩物件的加法,友元
c3.display();
c3 = c1.operator+(c2); //呼叫成員函式
c3.display();
cout << "物件Complex 和 整型數的加法" << "\n" << endl;
c3 = operator+(c1, 10); //物件與整型數的加法,呼叫的友元
c3.display();
c3 = c1.operator+(10); //物件與整型數的加 法, 呼叫成員函式
c3.display();
cout << "使用前置++運算子 遞增Complex物件" << "\n" << endl;
//前置++ 實現, 成員友元實現
c3 = c1.operator++();
c1.display();
c3.display();
c3 = operator++(c1);
c1.display();
c3.display();
//後置++ 實現, 成員友元實現
cout << "使用後置 ++運算子 遞增Complex物件" << "\n" << endl;
c3 = operator++(c1, 0);
c1.display();
c3.display();
c3 = c1.operator++(0);
c1.display();
c3.display();
cout << "使用過載輸入輸出運算子輸出Complex物件" << "\n" << endl;
// 使用過載輸入輸出運算子,過載輸入輸出運算子必須是友元函式
cin >> c1;
cout << "輸出物件c1的資料成員的值:" << c1 << endl;
cout << "使用 過載賦值運算子" << "\n" << endl;
c3 = c1;
cout << "輸出c3 資料成員的值:" << c3 << endl;
cout << "輸出c1 資料成員的值:" << c1 << endl;
//記住,下標運算子必須以成員函式進行過載
cout << "使用 過載下標運算子" << "\n" << endl;
cout << "輸出 c1 中arr陣列的第一個值:" << c1[0] << endl;
cout << "輸出 c2 中arr陣列的第一個值:" << c2[0] << endl;
cout << "輸出 c3 中arr陣列的第一個值:" << c3[0] << endl;
system("pause");
return 0;
}
輸出結果為: