當(dāng)運(yùn)算符作用于類類型的運(yùn)算對象時(shí),可以通過運(yùn)算符重載重新定義該運(yùn)算符的含義
- 重載的運(yùn)算符是具有特殊名字的函數(shù):它們的名字由關(guān)鍵字operator和其后要定義的運(yùn)算符號共同組成。和其他函數(shù)一樣勺鸦,重載的運(yùn)算符也包含返回類型徐绑,參數(shù)列表以及函數(shù)體
- 重載運(yùn)算符函數(shù)的參數(shù)數(shù)量與該運(yùn)算符作用的運(yùn)算對象數(shù)量一樣多队橙。除了重載的函數(shù)調(diào)用運(yùn)算符operator()之外妥箕,其他重載運(yùn)算符不能含有默認(rèn)實(shí)參鸿摇。
- 如果一個(gè)運(yùn)算符函數(shù)是成員函數(shù)石景,則它的第一個(gè)(左側(cè))運(yùn)算對象綁定到隱式的this指針上,因此拙吉,成員運(yùn)算符函數(shù)的顯式參數(shù)數(shù)量比運(yùn)算符的運(yùn)算對象總數(shù)少一個(gè)
- 對于一個(gè)運(yùn)算符函數(shù)來說潮孽,它或者是類的成員,或者至少含有一個(gè)類類型的參數(shù)筷黔,這一約定意味著當(dāng)運(yùn)算符作用于內(nèi)置類型的運(yùn)算對象時(shí)往史,我們無法改變該運(yùn)算符的含義。
- 我們只能重載已有的大多數(shù)運(yùn)算符佛舱,而無權(quán)發(fā)明新的運(yùn)算符號椎例。
- 對于一個(gè)重載的運(yùn)算符來說挨决,其優(yōu)先級和結(jié)合律與對應(yīng)的內(nèi)置運(yùn)算符保持一致。
- 我們既可以直接將運(yùn)算符作用于類型正確的實(shí)參订歪,從而間接“調(diào)用”重載的運(yùn)算符函數(shù)脖祈,也可以像調(diào)用普通(成員)函數(shù)一樣調(diào)用運(yùn)算符函數(shù)。
- 某些運(yùn)算符指定了運(yùn)算對象求值的順序刷晋。因?yàn)槭褂弥剌d的運(yùn)算符本質(zhì)上是一次函數(shù)調(diào)用盖高,所以這些求值順序的規(guī)則無法應(yīng)用到重載的運(yùn)算符上。特別是眼虱,邏輯與&&喻奥,邏輯或||和逗號運(yùn)算符。同時(shí)也不應(yīng)該重載取地址符蒙幻。
- 重載運(yùn)算符的返回類型通常情況下應(yīng)該與其內(nèi)置版本的返回類型兼容映凳。一般情況下胆筒,只有當(dāng)操作的含義對于用戶來說清晰明了時(shí)才重載運(yùn)算符邮破。
- 當(dāng)我們定義重載的運(yùn)算符時(shí),必須首先決定是將其聲明為類的成員函數(shù)還是聲明為一個(gè)普通的非成員函數(shù)仆救,下面是一些有助于抉擇的準(zhǔn)則:
- 賦值(=)抒和,下標(biāo)([]),調(diào)用(())和成員訪問箭頭運(yùn)算符(->)必須是成員
- 復(fù)合賦值運(yùn)算符一般是成員彤蔽,但并非必須
- 改變對象狀態(tài)的運(yùn)算符或者與給定類型密切相關(guān)的運(yùn)算符摧莽,通常應(yīng)該是成員
- 具有對稱性的運(yùn)算符應(yīng)該是普通的非成員函數(shù)
- 當(dāng)我們把運(yùn)算符定義成成員函數(shù)時(shí),它的左側(cè)運(yùn)算對象必須是運(yùn)算符所屬類的一個(gè)對象顿痪。
- 如我們所知镊辕,IO標(biāo)準(zhǔn)庫分別使用了<<和>>執(zhí)行輸出和輸入操作,對于這兩個(gè)運(yùn)算符來說蚁袭,IO庫定義了用其讀寫內(nèi)置類型的版本征懈,而類則需要自定義適合其對象的新版本以支持IO操作。
- 舉個(gè)例子:
ostream& operator<<(ostream &os,const Sales_data &item){
os<<item.isbn()<<" "<<items.units_sold;
return os;
}
與iostream標(biāo)準(zhǔn)庫兼容的輸入輸出運(yùn)算符必須是普通的非成員函數(shù)揩悄,而不能是類的成員函數(shù)卖哎。否則,它們的左側(cè)運(yùn)算對象將是我們的類的一個(gè)對象删性,這顯然不可能亏娜。當(dāng)然,IO運(yùn)算符通常需要讀寫類的非公有數(shù)據(jù)成員蹬挺,所以IO運(yùn)算符一般被聲明為友元维贺。
- 對于輸入運(yùn)算符而言,也比較類似巴帮,不過第二個(gè)參數(shù)是非常量對象的引用溯泣,同時(shí)函數(shù)體內(nèi)要對流進(jìn)行檢測(比如通過if)群发,避免一些輸入錯(cuò)誤的影響。
- 通常情況下发乔,我們把算術(shù)和關(guān)系運(yùn)算符定義成非成員函數(shù)以允許運(yùn)算對象位置的轉(zhuǎn)換熟妓,因?yàn)檫@些運(yùn)算符一般不會改變運(yùn)算對象的狀態(tài),所以形參都是常量的引用栏尚。
- 如果類定義了operator==起愈,則這個(gè)類也應(yīng)該定義operator!=,同時(shí)其中之一的任務(wù)應(yīng)該委托給另外一方译仗,而不用重復(fù)書寫一套非常相似的邏輯抬虽。
- 如果存在唯一一種邏輯可靠的<定義,則應(yīng)該考慮為這個(gè)類定義<運(yùn)算符纵菌。如果類同時(shí)還包括==阐污,則當(dāng)且僅當(dāng)<的定義和==產(chǎn)生的結(jié)果一致時(shí)才定義<運(yùn)算符。
- 之前已經(jīng)介紹過拷貝賦值和移動賦值運(yùn)算符咱圆,此外笛辟,類還可以定義其他賦值運(yùn)算符以使用別的類型作為右側(cè)運(yùn)算對象。
比如序苏,vector可以使用花括號的元素列表作為參數(shù)手幢,實(shí)際上是利用了std::iniitializer_list<T> 這個(gè)類型作為參數(shù),以此類推忱详。 - 為了與下標(biāo)的原始定義兼容围来,下標(biāo)運(yùn)算符通常以所訪問元素的引用作為返回值。同時(shí)匈睁,我們最好同時(shí)定義下標(biāo)運(yùn)算符的常量版本和非常量版本监透,當(dāng)作用于一個(gè)常量對象時(shí),下標(biāo)運(yùn)算符返回常量引用確保我們不會賦值航唆。
- 定義遞增和遞減運(yùn)算符的類應(yīng)該同時(shí)定義前置和后置版本胀蛮,并且通常被定義為成員。
- 要想同時(shí)定義前置和后置佛点,需要解決一個(gè)問題醇滥,即普通的重載形式無法區(qū)分這兩種情況。為了解決這個(gè)問題超营,后置版本接受一個(gè)額外的(不被使用)的int類型的形參鸳玩,當(dāng)我們使用后置運(yùn)算符時(shí),編譯器為這個(gè)形參提供一個(gè)值為0的實(shí)參演闭。
ClassName operator++(int);
ClassName operator--(int);
很多時(shí)候不跟,后置版本可以通過調(diào)用前置版本來完成實(shí)際的工作。
同時(shí)米碰,如果想要顯式地調(diào)用后置版本窝革,需要為那個(gè)不被使用的int參數(shù)傳入一個(gè)值购城。
- 與大多數(shù)其他運(yùn)算符一樣,我們能令operator*完成任何我們指定的操作虐译。但是箭頭運(yùn)算符則不是這樣瘪板,它永遠(yuǎn)不能丟掉成員訪問這個(gè)最基本的含義,當(dāng)我們重載箭頭時(shí)漆诽,可以改變的是箭頭從哪個(gè)對象獲取成員侮攀。
對于形如point->mem的表達(dá)式來說,point必須是指向類對象的指針或者是一個(gè)重載operator->的類的對象厢拭。根據(jù)point類型的不同兰英,point->mem分別等價(jià)于:
(*point).mem; //point是一個(gè)內(nèi)置的指針類型
point.operator()->mem; //point是類的一個(gè)對象
所以很顯然,重載的箭頭運(yùn)算符必須返回類的指針或者自定義了箭頭運(yùn)算符的某個(gè)類的對象供鸠。
這兩個(gè)運(yùn)算符往往定義成const成員畦贸,因?yàn)樗麄円话悴桓淖儗ο蟮臓顟B(tài)。
- 如果類定義了調(diào)用運(yùn)算符楞捂,則該類的對象稱作函數(shù)對象薄坏,因?yàn)榭梢哉{(diào)用這種對象,我們說這些的對象”行為像函數(shù)一樣”泡一。
- 當(dāng)我們編寫了一個(gè)lambda后颤殴,編譯器將該表達(dá)式翻譯成一個(gè)未命名類的未命名對象觅廓,在產(chǎn)生的類中有一個(gè)重載的函數(shù)調(diào)用運(yùn)算符鼻忠。
- 當(dāng)一個(gè)lambda表達(dá)式通過引用捕獲變量時(shí),將由程序確保所引對象確實(shí)存在杈绸,因此編譯器可以直接使用帖蔓。而如果通過值捕獲變量,產(chǎn)生類必須為每個(gè)值捕獲的變量建立對應(yīng)的數(shù)據(jù)成員瞳脓,同時(shí)創(chuàng)建構(gòu)造函數(shù)用于初始化這些成員塑娇。
- 標(biāo)準(zhǔn)庫還定義了一組表示算術(shù),關(guān)系劫侧,邏輯運(yùn)算符的類埋酬,每個(gè)類分別定義了一個(gè)執(zhí)行命名操作的調(diào)用運(yùn)算符。由于屬于函數(shù)式編程的范疇烧栋,此處不贅述写妥。
- 前面的筆記提到過,如果構(gòu)造函數(shù)只接受一個(gè)實(shí)參审姓,則它實(shí)際上定義了轉(zhuǎn)換為此類類型的隱式轉(zhuǎn)換規(guī)則珍特,這種構(gòu)造函數(shù)也被稱為轉(zhuǎn)換構(gòu)造函數(shù)。
- 類型轉(zhuǎn)換運(yùn)算符(conversion operator)是類的一種特殊成員函數(shù)魔吐,它負(fù)責(zé)將一個(gè)類類型的值轉(zhuǎn)換成其他類型扎筒。一般形式如下:
operator type() const;
其中type表示某種類型莱找,類型轉(zhuǎn)換運(yùn)算符可以面向任意類型(除了void)進(jìn)行定義,只要該類型能作為函數(shù)的返回類型嗜桌。
比如:
class SmallInt{
public:
SmallInt(int i=0):val(i){}
operator int() const{return val;}
private:
size_t val;
};
其中奥溺,構(gòu)造函數(shù)將算術(shù)類型的值轉(zhuǎn)換成SmallInt對象,而類型轉(zhuǎn)換運(yùn)算符將SmallInt轉(zhuǎn)換成int骨宠。
- 一個(gè)類型轉(zhuǎn)換函數(shù)必須是類的成員函數(shù)谚赎;它不能聲明返回類型,形參列表也必須為空诱篷,且通常是const的壶唤。
- 在實(shí)踐中,類很少提供類型轉(zhuǎn)換運(yùn)算符棕所,因?yàn)榇蠖鄶?shù)情況下如果類型轉(zhuǎn)換自動發(fā)生用戶可能會感到意外而不是受到了幫助闸盔。不過定義向bool的類型轉(zhuǎn)換還是比較普遍的現(xiàn)象。
在早期的版本中琳省,因?yàn)閎ool是一個(gè)算術(shù)類型迎吵,所以類類型被轉(zhuǎn)換成bool后能被用于任何需要算術(shù)類型的上下中,比如:
int i=42;
cin << i;
該代碼能使用istream的bool轉(zhuǎn)換针贬,接著提升至int并左移42位击费,這一結(jié)果不可謂不出人意料。
- 為了防止上述異常發(fā)生桦他,C++11標(biāo)準(zhǔn)引入了顯式的類型轉(zhuǎn)換運(yùn)算符
class SmallInt{
public:
explicit operator int() const {return val;}
...
}
和顯式的構(gòu)造函數(shù)一樣蔫巩,編譯器不會將一個(gè)顯式的類型轉(zhuǎn)換運(yùn)算符用于隱式類型轉(zhuǎn)換。
SmallInt s1=3; //正確快压,構(gòu)造函數(shù)非顯式
s1+3; //錯(cuò)誤:此處需要隱式的類型轉(zhuǎn)換,但運(yùn)算符是顯式的
static_cast<int>(s1)+3; //正確蔫劣,顯式地請求類型轉(zhuǎn)換
該規(guī)定存在一個(gè)例外坪郭,如果表達(dá)式被用作條件,則編譯器會將顯式的類型轉(zhuǎn)換自動應(yīng)用于它脉幢。由于向bool的類型轉(zhuǎn)換通常用在條件部分歪沃,所以operator bool一般也定義成explicit的。
- 類類型轉(zhuǎn)換的定義非常容易出現(xiàn)二義性嫌松,除了顯式地向bool類型的轉(zhuǎn)換之外沪曙,我們應(yīng)該盡量避免定義類型轉(zhuǎn)換函數(shù)。具體的二義性規(guī)則此處不贅述豆瘫,可以簡單地人為判斷出來珊蟀。
- 重載的運(yùn)算符也是重載的函數(shù),但候選函數(shù)集要比我們使用調(diào)用運(yùn)算符調(diào)用函數(shù)時(shí)更大,因?yàn)槲覀儫o法通過語法形式來區(qū)分到底使用的是成員函數(shù)還是非成員函數(shù)育灸,這同樣會有可能引發(fā)二義性問題腻窒。一般來說,如果我們對同一個(gè)類既提供了轉(zhuǎn)換目標(biāo)是算術(shù)類型的類型轉(zhuǎn)換磅崭,也提供了重載的運(yùn)算符儿子,則將會遇到重載運(yùn)算符與內(nèi)置運(yùn)算符的二義性問題。