目錄
1. 基本概念
1.1 直接調(diào)用一個重載的運算符函數(shù)
1.2 某些運算符不應(yīng)該被重載
1.3 使用與內(nèi)置類型一致的含義
1.4 賦值和復(fù)合賦值運算符
1.5 選擇作為成員或者非成員
2. 輸入和輸出運算符
2.1 <<輸出運算符重載
2.2 >>輸入運算符重載
3. 算術(shù)和關(guān)系運算符
3.1 算數(shù)運算符重載
3.2 ==相等運算符重載
3.3 關(guān)系運算符重載
4. 賦值運算符
4.1 =賦值運算符重載
4.2 復(fù)合賦值運算符重載
5. 遞增和遞減運算符
5.1 前置遞增/遞減運算符重載
5.2 后置遞增/遞減運算符重載
6. 函數(shù)調(diào)用運算符
1. 基本概念
重載的運算符是具有特殊名字的函數(shù):它們的名字由關(guān)鍵字operator和其后要定義的運算符號共同組成。和其他函數(shù)一樣,重載的運算符也包含返回類型、參數(shù)列表以及函數(shù)體。
重載運算符函數(shù)的參數(shù)數(shù)量與該運算符作用的運算對象數(shù)量一樣多。一元運算符有一個參數(shù),二元運算符有兩個。對于二元運算符來說,左側(cè)運算對象傳遞給第一個參數(shù),而右側(cè)運算對象傳遞給第二個參數(shù)。除了重載的函數(shù)調(diào)用運算符operator()之外,其他重載運算符不能含有默認實參。
如果一個運算符函數(shù)是成員函數(shù),則它的第一個(左側(cè))運算對象綁定到隱式的this指針上,因此,成員運算符函數(shù)的(顯式)參數(shù)數(shù)量比運算符的運算對象總數(shù)少一個。
對于一個運算符函數(shù)來說,它或者是類的成員,或者至少含有一個類類型的參數(shù):
int operator+(int,int); // err 不能為int重定義內(nèi)置的運算符
這一約定意味著當運算符作用于內(nèi)置類型的運算對象時,我們無法改變該運算符的含義。
不能重載的運算符:.? ? ? ? .*? ? ? ? ::? ? ? ? ? :? ? ? ? sizeof
我們只能重載已有的運算符,而無權(quán)發(fā)明新的運算符號。例如,我們不能提供operator**來執(zhí)行冪操作。
有四個符號(+、-、*、&)既是一元運算符也是二元運算符,所有這些運算符都能被重載,從參數(shù)的數(shù)量我們可以推斷到底定義的是哪種運算符。
對于一個重載的運算符來說,其優(yōu)先級和結(jié)合律與對應(yīng)的內(nèi)置運算符保持一致。不考慮運算對象類型的話,
x == y + z;
永遠等價于
x == (y + z);
1.1 直接調(diào)用一個重載的運算符函數(shù)
通常情況下,我們將運算符作用于類型正確的實參,從而以這種間接方式“調(diào)用”重載的運算符函數(shù)。然而,我們也能像調(diào)用普通函數(shù)一樣直接調(diào)用運算符函數(shù),先指定函數(shù)名字,然后傳入數(shù)量正確、類型適當?shù)膶崊ⅲ?/p>
// 一個非成員運算符函數(shù)的等價調(diào)用
data1 + data2; // 普通的表達式
operator+(data1, data2); // 等價的函數(shù)調(diào)用
這兩次調(diào)用是等價的,它們都調(diào)用了非成員函數(shù)operator+,傳入data1作為第一個實參、傳入data2作為第二個實參。
我們像調(diào)用其他成員函數(shù)一樣顯式地調(diào)用成員運算符函數(shù)。具體做法是,首先指定運行函數(shù)的對象(或指針)的名字,然后使用點運算符(或箭頭運算符)訪問希望調(diào)用的函數(shù):
data1 += data2; // 基于“調(diào)用”的表達式
data1.operator+=(data2); // 對成員運算符函數(shù)的等價調(diào)用
這兩條語句都調(diào)用了成員函數(shù)operator+=,將this綁定到data1的地址、將data2作為實參傳入了函數(shù)。
1.2 某些運算符不應(yīng)該被重載
某些運算符指定了運算對象求值的順序。因為使用重載的運算符本質(zhì)上是一次函數(shù)調(diào)用,所以這些關(guān)于運算對象求值順序的規(guī)則無法應(yīng)用到重載的運算符上。特別是,邏輯與運算符、邏輯或運算符和逗號運算符的運算對象求值順序規(guī)則無法保留下來。除此之外,&&和||運算符的重載版本也無法保留內(nèi)置運算符的短路求值屬性,兩個運算對象總是會被求值。
因為上述運算符的重載版本無法保留求值順序和/或短路求值屬性,因此不建議重載它們。當代碼使用了這些運算符的重載版本時,用戶可能會突然發(fā)現(xiàn)他們一直習慣的求值規(guī)則不再適用了。
還有一個原因使得我們一般不重載逗號運算符和取地址運算符:C++語言已經(jīng)定義了這兩種運算符用于類類型對象時的特殊含義,這一點與大多數(shù)運算符都不相同。因為這兩種運算符已經(jīng)有了內(nèi)置的含義,所以一般來說它們不應(yīng)該被重載,否則它們的行為將異于常態(tài),從而導(dǎo)致類的用戶無法適應(yīng)。
1.3 使用與內(nèi)置類型一致的含義
當你開始設(shè)計一個類時,首先應(yīng)該考慮的是這個類將提供哪些操作。在確定類需要哪些操作之后,才能思考到底應(yīng)該把每個類操作設(shè)成普通函數(shù)還是重載的運算符。如果某些操作在邏輯上與運算符相關(guān),則它們適合于定義成重載的運算符:
-
如果類執(zhí)行IO操作,則定義移位運算符使其與內(nèi)置類型的IO保持一致。
-
如果類的某個操作是檢查相等性,則定義operator==;如果類有了operator==,意味著它通常也應(yīng)該有operator!=。
-
如果類包含一個內(nèi)在的單序比較操作,則定義operator<;如果類有了operator<,則它也應(yīng)該含有其他關(guān)系操作。
-
重載運算符的返回類型通常情況下應(yīng)該與其內(nèi)置版本的返回類型兼容:邏輯運算符和關(guān)系運算符應(yīng)該返回bool,算術(shù)運算符應(yīng)該返回一個類類型的值,賦值運算符和復(fù)合賦值運算符則應(yīng)該返回左側(cè)運算對象的一個引用。
1.4 賦值和復(fù)合賦值運算符
賦值運算符的行為與復(fù)合版本的類似:賦值之后,左側(cè)運算對象和右側(cè)運算對象的值相等,并且運算符應(yīng)該返回它左側(cè)運算對象的一個引用。重載的賦值運算應(yīng)該繼承而非違背其內(nèi)置版本的含義。
如果類含有算術(shù)運算符或者位運算符,則最好也提供對應(yīng)的復(fù)合賦值運算符。無須贅言,+=運算符的行為顯然應(yīng)該與其內(nèi)置版本一致,即先執(zhí)行+,再執(zhí)行=。
1.5 選擇作為成員或者非成員
當我們定義重載的運算符時,必須首先決定是將其聲明為類的成員函數(shù)還是聲明為一個普通的非成員函數(shù)。在某些時候我們別無選擇,因為有的運算符必須作為成員;另一些情況下,運算符作為普通函數(shù)比作為成員更好。
下面的準則有助于我們在將運算符定義為成員函數(shù)還是普通的非成員函數(shù)做出抉擇:
- 賦值(=)、下標([])、調(diào)用(())和成員訪問箭頭(->)運算符必須是成員。
- 復(fù)合賦值運算符一般來說應(yīng)該是成員,但并非必須,這一點與賦值運算符略有不同。
- 改變對象狀態(tài)的運算符或者與給定類型密切相關(guān)的運算符,如遞增、遞減和解引用運算符,通常應(yīng)該是成員。
- 具有對稱性的運算符可能轉(zhuǎn)換任意一端的運算對象,例如算術(shù)、相等性、關(guān)系和位運算符等,因此它們通常應(yīng)該是普通的非成員函數(shù)。
程序員希望能在含有混合類型的表達式中使用對稱性運算符。例如,我們能求一個int和一個double的和,因為它們中的任意一個都可以是左側(cè)運算對象或右側(cè)運算對象,所以加法是對稱的。如果我們想提供含有類對象的混合類型表達式,則運算符必須定義成非成員函數(shù)。
當我們把運算符定義成成員函數(shù)時,它的左側(cè)運算對象必須是運算符所屬類的一個對象。例如:
string s = "world";
string t = s + "!"; // ok 我們能把一個const char*加到一個string對象中
string u = "hi" + s; // 如果+是string的成員,則產(chǎn)生錯誤
如果operator+是string類的成員,則上面的第一個加法等價于s.operator+("!")。同樣的,"hi"+s等價于"hi".operator+(s)。顯然"hi"的類型是const char*,這是一種內(nèi)置類型,根本就沒有成員函數(shù)。
因為string將+定義成了普通的非成員函數(shù),所以"hi"+s等價于operator+("hi",s)。和任何其他函數(shù)調(diào)用一樣,每個實參都能被轉(zhuǎn)換成形參類型。唯一的要求是至少有一個運算對象是類類型,并且兩個運算對象都能準確無誤地轉(zhuǎn)換成string。
有一個Date類:
class Date
{
public:
// 獲取某年某月的天數(shù)
int GetMonthDay(int year, int month) const
{
assert(month > 0 && month < 13);
int monthArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400) == 0))
{
return 29;
}
else
{
return monthArray[month];
}
}
// 構(gòu)造函數(shù)
Date(int year = 1900, int month = 1, int day = 1)
{
if (month > 0 && month < 13 && (day > 0 && day <= GetMonthDay(year, month)))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "日期非法" << endl;
}
}
private:
int _year;
int _month;
int _day;
};
2. 輸入和輸出運算符
如我們所知,IO標準庫分別使用>>和<<執(zhí)行輸入和輸出操作。對于這兩個運算符來說,IO庫定義了用其讀寫內(nèi)置類型的版本,而類則需要自定義適合其對象的新版本以支持IO操作。
2.1 <<輸出運算符重載
通常情況下,輸出運算符的第一個形參是一個非常量ostream對象的引用。之所以ostream是非常量是因為向流寫入內(nèi)容會改變其狀態(tài);而該形參是引用是因為我們無法直接復(fù)制一個ostream對象。
第二個形參一般來說是一個常量的引用,該常量是我們想要打印的類類型。第二個形參是引用的原因是我們希望避免復(fù)制實參;而之所以該形參可以是常量是因為(通常情況下)打印對象不會改變對象的內(nèi)容。
為了與其他輸出運算符保持一致,operator<<一般要返回它的ostream形參。
class Date
{
// <<輸出運算符重載
friend ostream& operator<<(ostream& out, const Date& d);
public:
int GetMonthDay(int year, int month) const
{
// ...
}
Date(int year = 1900, int month = 1, int day = 1)
{
// ...
}
private:
int _year;
int _month;
int _day;
};
// <<輸出運算符重載
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日";
return out;
}
用于內(nèi)置類型的輸出運算符不太考慮格式化操作,尤其不會打印換行符,用戶希望類的輸出運算符也像如此行事。如果運算符打印了換行符,則用戶就無法在對象的同一行內(nèi)接著打印一些描述性的文本了。相反,令輸出運算符盡量減少格式化操作可以使用戶有權(quán)控制輸出的細節(jié)。
與iostream標準庫兼容的輸入輸出運算符必須是普通的非成員函數(shù),而不能是類的成員函數(shù)。否則,它們的左側(cè)運算對象將是我們的類的一個對象:
Date d;
d << cout; // 如果operator<<是Date的成員
假設(shè)輸入輸出運算符是某個類的成員,則它們也必須是istream或ostream的成員。然而,這兩個類屬于標準庫,并且我們無法給標準庫中的類添加任何成員。
因此,如果我們希望為類自定義IO運算符,則必須將其定義成非成員函數(shù)。當然,IO運算符通常需要讀寫類的非公有數(shù)據(jù)成員,所以IO運算符一般被聲明為友元。
2.2 >>輸入運算符重載
通常情況下,輸入運算符的第一個形參是運算符將要讀取的流的引用,第二個形參是將要讀入到的(非常量)對象的引用。該運算符通常會返回某個給定流的引用。第二個形參之所以必須是個非常量是因為輸入運算符本身的目的就是將數(shù)據(jù)讀入到這個對象中。
class Date
{
// >>輸入運算符重載
friend istream& operator>>(istream& in, Date& d);
public:
int GetMonthDay(int year, int month) const
{
// ...
}
Date(int year = 1900, int month = 1, int day = 1)
{
// ...
}
private:
int _year;
int _month;
int _day;
};
// >>輸入運算符重載
istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
if (!in)
{
d = Date(); // 輸入失?。簩ο蟊毁x予默認的狀態(tài)
}
return in;
}
if語句檢查讀取操作是否成功,如果發(fā)生了IO錯誤,則運算符將給定的對象重置為默認的Date,這樣可以確保對象處于正確的狀態(tài)。
輸入運算符必須處理輸入可能失敗的情況,而輸出運算符不需要。
在執(zhí)行輸入運算符時可能發(fā)生下列錯誤:
- 當流含有錯誤類型的數(shù)據(jù)時讀取操作可能失敗。
- 當讀取操作到達文件末尾或者遇到輸入流的其他錯誤時也會失敗。
3. 算術(shù)和關(guān)系運算符
通常情況下,我們把算術(shù)和關(guān)系運算符定義成非成員函數(shù)以允許對左側(cè)或右側(cè)的運算對象進行轉(zhuǎn)換。因為這些運算符一般不需要改變運算對象的狀態(tài),所以形參都是常量的引用。
3.1 算數(shù)運算符重載
算術(shù)運算符通常會計算它的兩個運算對象并得到一個新值,這個值有別于任意一個運算對象,常常位于一個局部變量之內(nèi),操作完成后返回該局部變量的副本作為其結(jié)果。如果類定義了算術(shù)運算符,則它一般也會定義一個對應(yīng)的復(fù)合賦值運算符。此時,最有效的方式是使用復(fù)合賦值來定義算術(shù)運算符:
定義為非成員函數(shù)(最好):
class Date
{
public:
int GetMonthDay(int year, int month) const
{
// ...
}
Date(int year = 1900, int month = 1, int day = 1)
{
// ...
}
// +=復(fù)合賦值運算符重載:日期+=天數(shù)
Date& operator+=(int day)
{
if (day < 0)
{
*this -= -day;
return *this;
}
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
}
// -=復(fù)合賦值運算符重載:日期-=天數(shù)
Date& operator-=(int day)
{
if (day < 0)
{
*this += -day;
return *this;
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
// +加號運算符重載:日期+天數(shù)
Date operator+(Date& d, int day)
{
Date tmp = d;
tmp += day;
return tmp;
}
// -減號運算符重載:日期-天數(shù)
Date operator-(Date& d, int day)
{
Date tmp = d;
tmp -= day;
return tmp;
}
定義為成員函數(shù):
class Date
{
public:
int GetMonthDay(int year, int month) const
{
// ...
}
Date(int year = 1900, int month = 1, int day = 1)
{
// ...
}
// +=復(fù)合賦值運算符重載:日期+=天數(shù)
Date& operator+=(int day)
{
if (day < 0)
{
*this -= -day;
return *this;
}
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
}
// -=復(fù)合賦值運算符重載:日期-=天數(shù)
Date& operator-=(int day)
{
if (day < 0)
{
*this += -day;
return *this;
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
// +加號運算符重載:日期+天數(shù)
Date operator+(int day) const
{
Date tmp(*this);
tmp += day;
return tmp;
}
// -減號運算符重載:日期-天數(shù)
Date operator-(int day) const
{
Date tmp(*this);
tmp -= day;
return tmp;
}
private:
int _year;
int _month;
int _day;
};
3.2 ==相等運算符重載
通常情況下,C++中的類通過定義相等運算符來檢驗兩個對象是否相等。也就是說,它們會比較對象的每一個數(shù)據(jù)成員,只有當所有對應(yīng)的成員都相等時才認為兩個對象相等。
定義為非成員函數(shù)(最好):
class Date
{
// ==相等運算符重載
friend bool operator==(const Date& d1, const Date& d2);
// !=不相等運算符重載
friend bool operator!=(const Date& d1, const Date& d2);
public:
int GetMonthDay(int year, int month) const
{
// ...
}
Date(int year = 1900, int month = 1, int day = 1)
{
// ...
}
private:
int _year;
int _month;
int _day;
};
// ==相等運算符重載
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}
// !=不相等運算符重載
bool operator!=(const Date& d1, const Date& d2)
{
return !(d1 == d2);
}
定義為成員函數(shù):
class Date
{
public:
int GetMonthDay(int year, int month) const
{
// ...
}
Date(int year = 1900, int month = 1, int day = 1)
{
// ...
}
// ==相等運算符重載
bool operator==(const Date& d) const
{
return _year == d._year && _month == d._month && _day == d._day;
}
// !=不相等運算符重載
bool operator!=(const Date& d) const
{
return !(*this == d);
}
private:
int _year;
int _month;
int _day;
};
就上面這些函數(shù)的定義本身而言,它們似乎比較簡單,也沒什么價值,對于我們來說重要的是從這些函數(shù)中體現(xiàn)出來的設(shè)計準則:
- 如果一個類含有判斷兩個對象是否相等的操作,則它顯然應(yīng)該把函數(shù)定義成operator==而非一個普通的命名函數(shù):因為用戶肯定希望能使用==比較對象,所以提供了==就意味著用戶無須再費時費力地學習并記憶一個全新的函數(shù)名字。此外,類定義了==運算符之后也更容易使用標準庫容器和算法。
- 如果類定義了operator==,則該運算符應(yīng)該能判斷一組給定的對象中是否含有重復(fù)數(shù)據(jù)。
- 通常情況下,相等運算符應(yīng)該具有傳遞性,換句話說,如果a==b和b==c都為真,則a==c也應(yīng)該為真。
- 如果類定義了operator==,則這個類也應(yīng)該定義operator!=。對于用戶來說,當他們能使用==時肯定也希望能使用!=,反之亦然。
- 相等運算符和不相等運算符中的一個應(yīng)該把工作委托給另外一個,這意味著其中一個運算符應(yīng)該負責實際比較對象的工作,而另一個運算符則只是調(diào)用那個真正工作的運算符。
3.3 關(guān)系運算符重載
定義為非成員函數(shù)(最好):
class Date
{
friend bool operator==(const Date& d1, const Date& d2);
// <關(guān)系運算符重載
friend bool operator<(const Date& d1, const Date& d2);
// <=關(guān)系運算符重載
friend bool operator<=(const Date& d1, const Date& d2);
// >關(guān)系運算符重載
friend bool operator>(const Date& d1, const Date& d2);
// >=關(guān)系運算符重載
friend bool operator>=(const Date& d1, const Date& d2);
public:
int GetMonthDay(int year, int month) const
{
// ...
}
Date(int year = 1900, int month = 1, int day = 1)
{
// ...
}
private:
int _year;
int _month;
int _day;
};
bool operator==(const Date& d1, const Date& d2)
{
// ...
}
// <關(guān)系運算符重載
bool operator<(const Date& d1, const Date& d2)
{
return d1._year < d2._year
|| (d1._year == d2._year && d1._month < d2._month)
|| (d1._year == d2._year && d1._month == d2._month && d1._day < d2._day);
}
// <=關(guān)系運算符重載
bool operator<=(const Date& d1, const Date& d2)
{
return d1 < d2 || d1 == d2;
}
// >關(guān)系運算符重載
bool operator>(const Date& d1, const Date& d2)
{
return !(d1 <= d2);
}
// >=關(guān)系運算符重載
bool operator>=(const Date& d1, const Date& d2)
{
return !(d1 < d2);
}
定義為成員函數(shù):
class Date
{
public:
int GetMonthDay(int year, int month) const
{
// ...
}
Date(int year = 1900, int month = 1, int day = 1)
{
// ...
}
bool operator==(const Date& d) const
{
// ...
}
// <關(guān)系運算符重載
bool operator<(const Date& d) const
{
return _year < d._year
|| (_year == d._year && _month < d._month)
|| (_year == d._year && _month == d._month && _day < d._day);
}
// <=關(guān)系運算符重載
bool operator<=(const Date& d) const
{
return *this < d || *this == d;
}
// >關(guān)系運算符重載
bool operator>(const Date& d) const
{
return !(*this <= d);
}
// >=關(guān)系運算符重載
bool operator>=(const Date& d) const
{
return !(*this < d);
}
private:
int _year;
int _month;
int _day;
};
4. 賦值運算符
4.1 =賦值運算符重載
我們可以重載賦值運算符。不論形參的類型是什么,賦值運算符都必須定義為成員函數(shù)。
class Date
{
public:
int GetMonthDay(int year, int month) const
{
// ...
}
Date(int year = 1900, int month = 1, int day = 1)
{
// ...
}
// =賦值運算符重載
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
4.2 復(fù)合賦值運算符重載
復(fù)合賦值運算符不非得是類的成員,不過我們還是傾向于把包括復(fù)合賦值在內(nèi)的所有賦值運算都定義在類的內(nèi)部。為了與內(nèi)置類型的復(fù)合賦值保持一致,類中的復(fù)合賦值運算符也要返回其左側(cè)運算對象的引用。
class Date
{
public:
int GetMonthDay(int year, int month) const
{
// ...
}
Date(int year = 1900, int month = 1, int day = 1)
{
// ...
}
// +=復(fù)合賦值運算符重載:日期+=天數(shù)
Date& operator+=(int day)
{
if (day < 0)
{
*this -= -day;
return *this;
}
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
}
// -=復(fù)合賦值運算符重載:日期-=天數(shù)
Date& operator-=(int day)
{
if (day < 0)
{
*this += -day;
return *this;
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
5. 遞增和遞減運算符
在迭代器類中通常會實現(xiàn)遞增運算符(++)和遞減運算符(--),這兩種運算符使得類可以在元素的序列中前后移動。C++語言并不要求遞增和遞減運算符必須是類的成員,但是因為它們改變的正好是所操作對象的狀態(tài),所以建議將其設(shè)定為成員函數(shù)。
對于內(nèi)置類型來說,遞增和遞減運算符既有前置版本也有后置版本。同樣,我們也應(yīng)該為類定義兩個版本的遞增和遞減運算符。接下來我們首先介紹前置版本,然后實現(xiàn)后置版本。
5.1 前置++/--運算符重載
class Date
{
public:
int GetMonthDay(int year, int month) const
{
// ...
}
Date(int year = 1900, int month = 1, int day = 1)
{
// ...
}
Date& operator+=(int day)
{
// ...
}
Date& operator-=(int day)
{
// ...
}
// 前置遞增運算符重載
Date& operator++()
{
*this += 1;
return *this;
}
// 前置遞減運算符重載
Date& operator--()
{
*this -= 1;
return *this;
}
private:
int _year;
int _month;
int _day;
};
5.2 后置++/--運算符重載
要想同時定義前置和后置運算符,必須首先解決一個問題,即普通的重載形式無法區(qū)分這兩種情況。前置和后置版本使用的是同一個符號,意味著其重載版本所用的名字將是相同的,并且運算對象的數(shù)量和類型也相同。
為了解決這個問題,后置版本接受一個額外的(不被使用)int類型的形參。當我們使用后置運算符時,編譯器為這個形參提供一個值為0的實參。盡管從語法上來說后置函數(shù)可以使用這個額外的形參,但是在實際過程中通常不會這么做。這個形參的唯一作用就是區(qū)分前置版本和后置版本的函數(shù),而不是真的要在實現(xiàn)后置版本時參與運算。
class Date
{
public:
int GetMonthDay(int year, int month) const
{
// ...
}
Date(int year = 1900, int month = 1, int day = 1)
{
// ...
}
Date& operator+=(int day)
{
// ...
}
Date& operator-=(int day)
{
// ...
}
// 后置遞增運算符重載
Date operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
// 后置遞減運算符重載
Date operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
private:
int _year;
int _month;
int _day;
};
可以顯式地調(diào)用一個重載的運算符,其效果與在表達式中以運算符號的形式使用它完全一樣。如果我們想通過函數(shù)調(diào)用的方式調(diào)用后置版本,則必須為它的整型參數(shù)傳遞一個值:
d.operator++(0) ; // 調(diào)用后置版本的operator++
d.operator++(); // 調(diào)用前置版本的operator++
盡管傳入的值通常會被運算符函數(shù)忽略,但卻必不可少,因為編譯器只有通過它才能知道應(yīng)該使用后置版本。
5.2 前置++/--和后置++/--的對比
// 前置++
Date& operator++()
{
*this += 1;
return *this;
}
// 后置++
Date operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
// 前置--
Date& operator--()
{
*this -= 1;
return *this;
}
// 后置--
Date operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
前置++/--返回引用,后置++/--返回對象,顯然前置++/--效率高。
6. 函數(shù)調(diào)用運算符
如果類重載了函數(shù)調(diào)用運算符,則我們可以像使用函數(shù)一樣使用該類的對象。因為這樣的類同時也能存儲狀態(tài),所以與普通函數(shù)相比它們更加靈活。
舉個簡單的例子,下面這個名為absInt的struct含有一個調(diào)用運算符,該運算符負責返回其參數(shù)的絕對值:
struct absInt
{
int operator()(int val) const
{
return val < 0 ? -val : val;
}
};
這個類只定義了一種操作:函數(shù)調(diào)用運算符,它負責接受一個int類型的實參,然后返回該實參的絕對值。
我們使用調(diào)用運算符的方式是令一個absInt對象作用于一個實參列表,這一過程看起來非常像調(diào)用函數(shù)的過程:
int i = -42;
absInt absObj; // 含有函數(shù)調(diào)用運算符的對象
int ui = absObj(i);// 將i傳遞給absObj.operator()
即使absObj只是一個對象而非函數(shù),我們也能“調(diào)用”該對象。調(diào)用對象實際上是在運行重載的調(diào)用運算符。在此例中,該運算符接受一個int值并返回其絕對值。
函數(shù)調(diào)用運算符必須是成員函數(shù)。一個類可以定義多個不同版本的調(diào)用運算符,相互之間應(yīng)該在參數(shù)數(shù)量或類型上有所區(qū)別。文章來源:http://www.zghlxwxcb.cn/news/detail-673027.html
如果類定義了調(diào)用運算符,則該類的對象稱作函數(shù)對象(function object)。因為可以調(diào)用這種對象,所以我們說這些對象的“行為像函數(shù)一樣”。文章來源地址http://www.zghlxwxcb.cn/news/detail-673027.html
到了這里,關(guān)于【C++】運算符重載的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!