C++中運算符的重載
- 2021 年 7 月 5 日
- 筆記
運算符重載相當於運算符的函數重載,用於對已有的運算符進行重新定義,賦予其另一種功能,以適應不同的數據類型。我們之前提到過C++中的函數重載,可以根據形參的不同調用不同的函數,那麼運算符重載跟函數重載的實現形式差不多,運算符重載的一般寫法為返回值 operator運算符(參數列表)
。
下面就根據幾個簡要的例子來簡單實現一下加法,左移以及自增運算符的重載,還有賦值,關係運算符等可以自己實現。首先自定義一個 person
類,通過運算符重載,實現 對person
類的對象中 age
屬性的一系列操作。
class person{
public:
int age;
string name;
// 值傳遞構造函數
person(string name,int age);
// 拷貝構造函數
person(person& p);
void show(); // 回顯函數,顯示類內成員的數值
};
person::person(string name,int age){
this->age = age; // this指針區分調用者
this->name = name;
}
person::person(person& p){
this->age = p.age;
this->name = p.name;
}
void person::show(){
cout << "name: " << name << "\tage: " << age << endl;
}
一、加法運算符重載
運算符重載可以表示為成員函數,也可以表示為全局函數
1.1 成員函數
class person{
public:
int age;
string name;
// 值傳遞構造函數
person(string name,int age);
// 拷貝構造函數
person(person& p);
void show();
person operator+(person& p); // 加法重載函數
};
// 加法重載函數的實現,第一個`person`代表返回值,第二個`person`代表域
person person::operator+(person& p){
person tmp(p); // 調用拷貝構造函數給對象 p 賦值
tmp.age = this->age + p.age;
tmp.name = this->name; // 將第一個變數的 name 成員作為 name 的結果
return tmp;
}
person p1("張三", 25); // 調用值傳遞拷貝函數給對象賦值
person p2("李四", 27); // 調用值傳遞靠別函數給對象賦值
person p3 = p1+p2; // 加法運算符重載,實現兩個類中的 age 成員相加
p3.show();
上述重載的過程其實就是 p1
調用 operator+
函數,相當於 p1.operator+(p2)
。在這個過程中,p2
作為參數傳遞給 operator
,完成相加後,由於返回類型為值傳遞,因此 return *this
其實就是返回 p1
拷貝出來的一個副本,必須在調用時重新賦值。輸出結果如下:
name: 張三 age: 52
1.2 全局函數寫法
在成員函數的寫法中,由於在調用加法重載時已經指定了一個對象 ( this 指針指向的對象 ),所以重載函數內只需要再傳遞一個參數即可。但是全局函數不屬於任何一個對象,因此在進行重載時需要傳入兩個參數。函數實現如下:
person operator+(person& p1, person& p2){
person tmp(p1); // 調用拷貝構造函數給對象 p 賦值
tmp.age = p1.age + p2.age;
tmp.name = p1.name; // 將第一個變數的 name 成員作為 name 的結果
return tmp;
}
person p1("張三", 25); // 調用值傳遞拷貝函數給對象賦值
person p2("李四", 27); // 調用值傳遞靠別函數給對象賦值
person p3 = p1+p2; // 加法運算符重載,實現兩個類中的 age 成員相加
p3.show();
輸出結果如下:
name: 張三 age: 52
1.3 鏈式編程
對於內置數據類型的加法運算符來說,可以實現 a+b+c
類型的操作,這種情況下先執行 a+b
,返回值再與 c
相加。這樣就必須確保返回值也是 person
數據類型。根據這個結論,上述兩種運算符重載的寫法返回值均為 person
類型,因此鏈式編程的實現為:
person p1("張三", 25);
person p2("李四", 27);
person p3("王五", 32);
person p4 = p1+p2+p3;
p4.show();
輸出結果為:
name: 張三 age: 84
二、左移運算符重載
左移運算符主要用於 cout << "abs"
等的輸出,也可以表示為成員函數或者全局函數。根據對比發現,左移運算符需要兩個參數,cout
和 person
,且已知 cout
屬於 ostream
類。
2.1 成員函數寫法
成員函數只能實例化一個 person
對象,然後 cout
作為函數參數,寫法如下:
class person{
public:
int age;
string name;
// 值傳遞構造函數
person(string name,int age);
// 拷貝構造函數
person(person& p);
void show();
person operator+(person& p); // 加法運算符重載
void operator<<(ostream& cout); // 左移運算符重載
};
// 左移運算符重載函數實現,由於 cout 全局只能有一個,若使用值傳遞的方式,則在傳遞過程中需要進行拷貝。因此只能使用引用傳遞的方式
void person::operator<<(ostream& cout){
cout << "name: " << name << "\tage: " << age << endl;
}
person p1("張三", 25);
p1 << cout; // 其實就是 p1.operator<<(cout) 的簡寫
輸出結果為:
name: 張三 age: 25
這種寫法與我么通常使用的方式剛好相反,因此左移運算符一般不採取成員函數的形式來實現,通常使用全局函數來實現。在這裡如果對引用不是很清楚的可以移步另一篇文章:C++中指針與引用詳解 – ZhiboZhao – 部落格園 (cnblogs.com)。
2.2 全局函數寫法
void operator<<(ostream& cout, person p){
cout << "name: " <<p.name << "\tage: " << p.age << endl;
}
person p1("張三", 25);
cout << p1;
輸出結果為:
name: 張三 age: 25
2.3 鏈式編程
通常使用左移運算符時,能夠實現 cout << a << b <<...<< endl
的效果,此過程中先執行 cout << a
,返回值再執行 下一個左移運算符。因此返回值必須是 ostream
類型。
// 返回值為 ostream 類型,又因為 cout 只有一個,因此以引用的方式返回
ostream& operator<<(ostream& cout, person p){
cout << "name: " <<p.name << "\tage: " << p.age << endl;
return cout;
}
person p1("張三", 25);
person p2("李四", 27);
cout << p1 << p2;
輸出結果為:
name: 張三 age: 25
name: 李四 age: 27
三、遞增運算符重載
遞增運算符++有兩種表現形式,分為前置和後置。
- 當為前置方式時”++p”,p的值先自增1,然後再返回增1後的p值
- 當為後置方式時”p++”,先返回p的值,p的值再自增1
比如:p=5
, 那麼 a = p++
執行結束後 a=5, p=6
, 如果 a=++p
執行結束後,a=6,p=6
。所以我們重載運算符需要分別實現這兩種形式。總的來說,前置運算符和後置運算符如果在不使用返回值的情況下,二者的作用一樣,都是使參數自增;當使用返回值時,前置運算符返回自增後的參數,而後置運算符返回自增之前的參數。
3.1 成員函數寫法
3.1.1 前置運算符實現
前置運算符的作用:1)自增 2)返回自增之後的參數,因此實現程式碼為:
class person{
public:
int age;
string name;
// 值傳遞構造函數
person(string name,int age);
// 拷貝構造函數
person(person& p);
void show();
person operator++(); // 前置自增運算符
void operator<<(ostream& cout); // 左移運算符重載
};
person person::operator++(){
age++; // age自增
return *this; // 返回自增之後的對象
}
person p1("張三", 25);
person p3 = ++p1; // 調用自增運算符重載函數
cout << "p3: " << p3;
cout << "p1: " << p1;
輸出結果如下:
p3: name: 張三 age: 26 // 前置自增運算符的返回值
p1: name: 張三 age: 26 // 自增之後
根據輸出結果可以知道,重載的前置自增運算符能夠實現期望的功能。
3.1.2 後置運算符實現
後置運算符的作用:1)自增 2)返回自增之前的參數,在函數內定義 int
佔位符作為形參,來實現與前置自增運算符的區分。因此實現程式碼為:
class person{
public:
int age;
string name;
// 值傳遞構造函數
person(string name,int age);
// 拷貝構造函數
person(person& p);
void show();
person operator+(person& p);
person operator++(); // 前置自增運算符
person operator++(int); // 後置自增運算符
void operator<<(ostream& cout); // 左移運算符重載
};
person p2("李四", 27);
person p4 = p2++; // 調用後置運算符
cout << "p4: " << p4;
cout << "p2: " << p2;
輸出結果如下:
p4: name: 李四 age: 27 // 後置自增運算符的返回值
p2: name: 李四 age: 28 // 自增之後
3.2 全局函數寫法
需要注意的是,由於全局函數不屬於任何一個對象,因此形參為引用或者指針傳遞時才能修改原數據。程式碼實現如下:
// 前置自增運算符實現
person operator++(person& p){
p.age++;
return p;
}
// 後置自增運算符實現
person operator++(person& p, int){
person tmp = p;
p.age++;
return tmp;
}
person p1("張三", 25);
person p2("李四", 27);
// 前置自增運算符測試
person p3 = ++p1;
cout << "p3: " << p3;
cout << "p1: " << p1;
// 後置自增運算符測試
person p4 = p2++;
cout << "p4: " << p4;
cout << "p2: " << p2;
輸出結果:
p3: name: 張三 age: 26 // 前置自增運算符的返回值
p1: name: 張三 age: 26 // 自增之後
p4: name: 李四 age: 27 // 後置自增運算符的返回值
p2: name: 李四 age: 28 // 自增之後
3.3 鏈式編程
由於後置自增運算符沒有鏈式實現,因此我們僅僅以前置自增運算符進行說明。首先我們先使用上述全局函數實現的自增重載函數進行鏈式測試,程式碼如下:
person p1("張三", 25);
person p3 = ++(++p1);
cout << "p3: " << p3;
cout << "p1: " << p1;
輸出如下:
p3: name: 張三 age: 27 // 兩次前置自增之後的返回值
p1: name: 張三 age: 26 // 兩次自增之後
這個輸出結果是不對的,我們期望經過兩次前置遞增之後,p1.age
同樣也遞增兩次變為 27
,p2.age
也遞增兩次變為 29
。通過分析發現:我們每次通過值傳遞的方式進行返回,所以在作為第一輪遞增之後,++p1
返回了一個臨時創建的副本,繼續進行下一次的遞增,導致第二次的遞增沒有發生在 p1
身上。解決這種問題的辦法就是將值返回改為返回引用。
以成員函數實現改寫後的程式碼為例:
class person{
public:
int age;
string name;
// 值傳遞構造函數
person(string name,int age);
// 拷貝構造函數
person(person& p);
void show();
person operator+(person& p);
person& operator++(); // 前置自增運算符
person operator++(int); // 後置自增運算符
void operator<<(ostream& cout); // 左移運算符重載
};
person& person::operator++(){
age++;
return *this;
}
person person::operator++(int){
person tmp(*this);
age++;
return tmp;
}
person p1("張三", 25);
person p3 = ++(++p1);
cout << "p3: " << p3;
cout << "p1: " << p1;
輸出結果如下:
p3: name: 張三 age: 27 // 兩次前置自增之後的返回值
p1: name: 張三 age: 27 // 兩次自增之後