[C++ Primer Note13] 重載運(yùn)算與類(lèi)型轉(zhuǎn)換

當(dāng)運(yùn)算符作用于類(lèi)類(lèi)型的運(yùn)算對(duì)象時(shí),可以通過(guò)運(yùn)算符重載重新定義該運(yùn)算符的含義

  1. 重載的運(yùn)算符是具有特殊名字的函數(shù):它們的名字由關(guān)鍵字operator和其后要定義的運(yùn)算符號(hào)共同組成。和其他函數(shù)一樣,重載的運(yùn)算符也包含返回類(lèi)型,參數(shù)列表以及函數(shù)體
  2. 重載運(yùn)算符函數(shù)的參數(shù)數(shù)量與該運(yùn)算符作用的運(yùn)算對(duì)象數(shù)量一樣多。除了重載的函數(shù)調(diào)用運(yùn)算符operator()之外,其他重載運(yùn)算符不能含有默認(rèn)實(shí)參。
  3. 如果一個(gè)運(yùn)算符函數(shù)是成員函數(shù),則它的第一個(gè)(左側(cè))運(yùn)算對(duì)象綁定到隱式的this指針上,因此,成員運(yùn)算符函數(shù)的顯式參數(shù)數(shù)量比運(yùn)算符的運(yùn)算對(duì)象總數(shù)少一個(gè)
  4. 對(duì)于一個(gè)運(yùn)算符函數(shù)來(lái)說(shuō),它或者是類(lèi)的成員,或者至少含有一個(gè)類(lèi)類(lèi)型的參數(shù),這一約定意味著當(dāng)運(yùn)算符作用于內(nèi)置類(lèi)型的運(yùn)算對(duì)象時(shí),我們無(wú)法改變?cè)撨\(yùn)算符的含義。
  5. 我們只能重載已有的大多數(shù)運(yùn)算符,而無(wú)權(quán)發(fā)明新的運(yùn)算符號(hào)。
  6. 對(duì)于一個(gè)重載的運(yùn)算符來(lái)說(shuō),其優(yōu)先級(jí)和結(jié)合律與對(duì)應(yīng)的內(nèi)置運(yùn)算符保持一致。
  7. 我們既可以直接將運(yùn)算符作用于類(lèi)型正確的實(shí)參,從而間接“調(diào)用”重載的運(yùn)算符函數(shù),也可以像調(diào)用普通(成員)函數(shù)一樣調(diào)用運(yùn)算符函數(shù)。
  8. 某些運(yùn)算符指定了運(yùn)算對(duì)象求值的順序。因?yàn)槭褂弥剌d的運(yùn)算符本質(zhì)上是一次函數(shù)調(diào)用,所以這些求值順序的規(guī)則無(wú)法應(yīng)用到重載的運(yùn)算符上。特別是,邏輯與&&,邏輯或||和逗號(hào)運(yùn)算符。同時(shí)也不應(yīng)該重載取地址符。
  9. 重載運(yùn)算符的返回類(lèi)型通常情況下應(yīng)該與其內(nèi)置版本的返回類(lèi)型兼容。一般情況下,只有當(dāng)操作的含義對(duì)于用戶(hù)來(lái)說(shuō)清晰明了時(shí)才重載運(yùn)算符。
  10. 當(dāng)我們定義重載的運(yùn)算符時(shí),必須首先決定是將其聲明為類(lèi)的成員函數(shù)還是聲明為一個(gè)普通的非成員函數(shù),下面是一些有助于抉擇的準(zhǔn)則:
  • 賦值(=),下標(biāo)([])調(diào)用(())成員訪問(wèn)箭頭運(yùn)算符(->)必須是成員
  • 復(fù)合賦值運(yùn)算符一般是成員,但并非必須
  • 改變對(duì)象狀態(tài)的運(yùn)算符或者與給定類(lèi)型密切相關(guān)的運(yùn)算符,通常應(yīng)該是成員
  • 具有對(duì)稱(chēng)性的運(yùn)算符應(yīng)該是普通的非成員函數(shù)
  1. 當(dāng)我們把運(yùn)算符定義成成員函數(shù)時(shí),它的左側(cè)運(yùn)算對(duì)象必須是運(yùn)算符所屬類(lèi)的一個(gè)對(duì)象。
  2. 如我們所知,IO標(biāo)準(zhǔn)庫(kù)分別使用了<<>>執(zhí)行輸出和輸入操作,對(duì)于這兩個(gè)運(yùn)算符來(lái)說(shuō),IO庫(kù)定義了用其讀寫(xiě)內(nèi)置類(lèi)型的版本,而類(lèi)則需要自定義適合其對(duì)象的新版本以支持IO操作。
  3. 舉個(gè)例子:
ostream& operator<<(ostream &os,const Sales_data &item){
    os<<item.isbn()<<" "<<items.units_sold;
    return os;
}

與iostream標(biāo)準(zhǔn)庫(kù)兼容的輸入輸出運(yùn)算符必須是普通的非成員函數(shù),而不能是類(lèi)的成員函數(shù)。否則,它們的左側(cè)運(yùn)算對(duì)象將是我們的類(lèi)的一個(gè)對(duì)象,這顯然不可能。當(dāng)然,IO運(yùn)算符通常需要讀寫(xiě)類(lèi)的非公有數(shù)據(jù)成員,所以IO運(yùn)算符一般被聲明為友元。

  1. 對(duì)于輸入運(yùn)算符而言,也比較類(lèi)似,不過(guò)第二個(gè)參數(shù)是非常量對(duì)象的引用,同時(shí)函數(shù)體內(nèi)要對(duì)流進(jìn)行檢測(cè)(比如通過(guò)if),避免一些輸入錯(cuò)誤的影響。
  2. 通常情況下,我們把算術(shù)和關(guān)系運(yùn)算符定義成非成員函數(shù)以允許運(yùn)算對(duì)象位置的轉(zhuǎn)換,因?yàn)檫@些運(yùn)算符一般不會(huì)改變運(yùn)算對(duì)象的狀態(tài),所以形參都是常量的引用
  3. 如果類(lèi)定義了operator==,則這個(gè)類(lèi)也應(yīng)該定義operator!=,同時(shí)其中之一的任務(wù)應(yīng)該委托給另外一方,而不用重復(fù)書(shū)寫(xiě)一套非常相似的邏輯。
  4. 如果存在唯一一種邏輯可靠的<定義,則應(yīng)該考慮為這個(gè)類(lèi)定義<運(yùn)算符。如果類(lèi)同時(shí)還包括==,則當(dāng)且僅當(dāng)<的定義和==產(chǎn)生的結(jié)果一致時(shí)才定義<運(yùn)算符。
  5. 之前已經(jīng)介紹過(guò)拷貝賦值和移動(dòng)賦值運(yùn)算符,此外,類(lèi)還可以定義其他賦值運(yùn)算符以使用別的類(lèi)型作為右側(cè)運(yùn)算對(duì)象。
    比如,vector可以使用花括號(hào)的元素列表作為參數(shù),實(shí)際上是利用了std::iniitializer_list<T> 這個(gè)類(lèi)型作為參數(shù),以此類(lèi)推。
  6. 為了與下標(biāo)的原始定義兼容,下標(biāo)運(yùn)算符通常以所訪問(wèn)元素的引用作為返回值。同時(shí),我們最好同時(shí)定義下標(biāo)運(yùn)算符的常量版本和非常量版本,當(dāng)作用于一個(gè)常量對(duì)象時(shí),下標(biāo)運(yùn)算符返回常量引用確保我們不會(huì)賦值。
  7. 定義遞增和遞減運(yùn)算符的類(lèi)應(yīng)該同時(shí)定義前置后置版本,并且通常被定義為成員
  8. 要想同時(shí)定義前置和后置,需要解決一個(gè)問(wèn)題,即普通的重載形式無(wú)法區(qū)分這兩種情況。為了解決這個(gè)問(wèn)題,后置版本接受一個(gè)額外的(不被使用)的int類(lèi)型的形參,當(dāng)我們使用后置運(yùn)算符時(shí),編譯器為這個(gè)形參提供一個(gè)值為0的實(shí)參。
ClassName operator++(int);
ClassName operator--(int);

很多時(shí)候,后置版本可以通過(guò)調(diào)用前置版本來(lái)完成實(shí)際的工作。
同時(shí),如果想要顯式地調(diào)用后置版本,需要為那個(gè)不被使用的int參數(shù)傳入一個(gè)值

  1. 與大多數(shù)其他運(yùn)算符一樣,我們能令operator*完成任何我們指定的操作。但是箭頭運(yùn)算符則不是這樣,它永遠(yuǎn)不能丟掉成員訪問(wèn)這個(gè)最基本的含義,當(dāng)我們重載箭頭時(shí),可以改變的是箭頭從哪個(gè)對(duì)象獲取成員。
    對(duì)于形如point->mem的表達(dá)式來(lái)說(shuō),point必須是指向類(lèi)對(duì)象的指針或者是一個(gè)重載operator->的類(lèi)的對(duì)象。根據(jù)point類(lèi)型的不同,point->mem分別等價(jià)于:
(*point).mem;   //point是一個(gè)內(nèi)置的指針類(lèi)型
point.operator()->mem;   //point是類(lèi)的一個(gè)對(duì)象

所以很顯然,重載的箭頭運(yùn)算符必須返回類(lèi)的指針或者自定義了箭頭運(yùn)算符的某個(gè)類(lèi)的對(duì)象
這兩個(gè)運(yùn)算符往往定義成const成員,因?yàn)樗麄円话悴桓淖儗?duì)象的狀態(tài)。

  1. 如果類(lèi)定義了調(diào)用運(yùn)算符,則該類(lèi)的對(duì)象稱(chēng)作函數(shù)對(duì)象,因?yàn)榭梢哉{(diào)用這種對(duì)象,我們說(shuō)這些的對(duì)象”行為像函數(shù)一樣”。
  2. 當(dāng)我們編寫(xiě)了一個(gè)lambda后,編譯器將該表達(dá)式翻譯成一個(gè)未命名類(lèi)的未命名對(duì)象,在產(chǎn)生的類(lèi)中有一個(gè)重載的函數(shù)調(diào)用運(yùn)算符。
  3. 當(dāng)一個(gè)lambda表達(dá)式通過(guò)引用捕獲變量時(shí),將由程序確保所引對(duì)象確實(shí)存在,因此編譯器可以直接使用。而如果通過(guò)值捕獲變量,產(chǎn)生類(lèi)必須為每個(gè)值捕獲的變量建立對(duì)應(yīng)的數(shù)據(jù)成員,同時(shí)創(chuàng)建構(gòu)造函數(shù)用于初始化這些成員。
  4. 標(biāo)準(zhǔn)庫(kù)還定義了一組表示算術(shù),關(guān)系,邏輯運(yùn)算符的類(lèi),每個(gè)類(lèi)分別定義了一個(gè)執(zhí)行命名操作的調(diào)用運(yùn)算符。由于屬于函數(shù)式編程的范疇,此處不贅述。
  5. 前面的筆記提到過(guò),如果構(gòu)造函數(shù)只接受一個(gè)實(shí)參,則它實(shí)際上定義了轉(zhuǎn)換為此類(lèi)類(lèi)型的隱式轉(zhuǎn)換規(guī)則,這種構(gòu)造函數(shù)也被稱(chēng)為轉(zhuǎn)換構(gòu)造函數(shù)。
  6. 類(lèi)型轉(zhuǎn)換運(yùn)算符(conversion operator)是類(lèi)的一種特殊成員函數(shù),它負(fù)責(zé)將一個(gè)類(lèi)類(lèi)型的值轉(zhuǎn)換成其他類(lèi)型。一般形式如下:

operator type() const;

其中type表示某種類(lèi)型,類(lèi)型轉(zhuǎn)換運(yùn)算符可以面向任意類(lèi)型(除了void)進(jìn)行定義,只要該類(lèi)型能作為函數(shù)的返回類(lèi)型。
比如:

class SmallInt{
public:
    SmallInt(int i=0):val(i){}
    operator int() const{return val;}
private:
    size_t val;
};

其中,構(gòu)造函數(shù)將算術(shù)類(lèi)型的值轉(zhuǎn)換成SmallInt對(duì)象,而類(lèi)型轉(zhuǎn)換運(yùn)算符將SmallInt轉(zhuǎn)換成int。

  1. 一個(gè)類(lèi)型轉(zhuǎn)換函數(shù)必須是類(lèi)的成員函數(shù);它不能聲明返回類(lèi)型,形參列表也必須為,且通常是const的。
  2. 在實(shí)踐中,類(lèi)很少提供類(lèi)型轉(zhuǎn)換運(yùn)算符,因?yàn)榇蠖鄶?shù)情況下如果類(lèi)型轉(zhuǎn)換自動(dòng)發(fā)生用戶(hù)可能會(huì)感到意外而不是受到了幫助。不過(guò)定義向bool的類(lèi)型轉(zhuǎn)換還是比較普遍的現(xiàn)象。
    在早期的版本中,因?yàn)閎ool是一個(gè)算術(shù)類(lèi)型,所以類(lèi)類(lèi)型被轉(zhuǎn)換成bool后能被用于任何需要算術(shù)類(lèi)型的上下中,比如:
int i=42;
cin << i;

該代碼能使用istream的bool轉(zhuǎn)換,接著提升至int并左移42位,這一結(jié)果不可謂不出人意料。

  1. 為了防止上述異常發(fā)生,C++11標(biāo)準(zhǔn)引入了顯式的類(lèi)型轉(zhuǎn)換運(yùn)算符
class SmallInt{
public:
    explicit operator int() const {return val;}
...
}

和顯式的構(gòu)造函數(shù)一樣,編譯器不會(huì)將一個(gè)顯式的類(lèi)型轉(zhuǎn)換運(yùn)算符用于隱式類(lèi)型轉(zhuǎn)換。

SmallInt s1=3; //正確,構(gòu)造函數(shù)非顯式
s1+3; //錯(cuò)誤:此處需要隱式的類(lèi)型轉(zhuǎn)換,但運(yùn)算符是顯式的
static_cast<int>(s1)+3; //正確,顯式地請(qǐng)求類(lèi)型轉(zhuǎn)換

該規(guī)定存在一個(gè)例外,如果表達(dá)式被用作條件,則編譯器會(huì)將顯式的類(lèi)型轉(zhuǎn)換自動(dòng)應(yīng)用于它。由于向bool的類(lèi)型轉(zhuǎn)換通常用在條件部分,所以operator bool一般也定義成explicit的。

  1. 類(lèi)類(lèi)型轉(zhuǎn)換的定義非常容易出現(xiàn)二義性,除了顯式地向bool類(lèi)型的轉(zhuǎn)換之外,我們應(yīng)該盡量避免定義類(lèi)型轉(zhuǎn)換函數(shù)。具體的二義性規(guī)則此處不贅述,可以簡(jiǎn)單地人為判斷出來(lái)。
  2. 重載的運(yùn)算符也是重載的函數(shù),但候選函數(shù)集要比我們使用調(diào)用運(yùn)算符調(diào)用函數(shù)時(shí)更大,因?yàn)槲覀儫o(wú)法通過(guò)語(yǔ)法形式來(lái)區(qū)分到底使用的是成員函數(shù)還是非成員函數(shù),這同樣會(huì)有可能引發(fā)二義性問(wèn)題。一般來(lái)說(shuō),如果我們對(duì)同一個(gè)類(lèi)既提供了轉(zhuǎn)換目標(biāo)是算術(shù)類(lèi)型的類(lèi)型轉(zhuǎn)換,也提供了重載的運(yùn)算符,則將會(huì)遇到重載運(yùn)算符與內(nèi)置運(yùn)算符的二義性問(wèn)題。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容