運算子過載相當於運算子的函式過載,用於對已有的運算子進行重新定義,賦予其另一種功能,以適應不同的資料型別。我們之前提到過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 // 兩次自增之後