c++primer——重載運算與類型轉換

1、重載的運算符是具有特殊名字的函數。對於一個運算符來說,它或者是函數成員,或者至少含有一個類類型的參數。我們只能重載已有的運算符。無法重載這個四個運算符:
::
.*
.
?:

2、部分制定了運算對象求職順序的運算符不該被重載,特別是,邏輯與,邏輯或、取地址和逗號運算符。原因是,這些關於運算對象求職順序的規則無法應用到重載的運算符上,去求值順序規則無法保留。並且&&和||的重載半杯也無法保留內置運算符的短路求值屬性,兩個運算對象總是會被求值。

3、定義了operator==通常也要有operator!=,並且在實現時只需要定義其中一個,另外一個返回!(args)即可。

4、如果一個類包含了內在的單序比較操作,如<,它通常也有其他關係操作。

5、重載運算符的返回類型通常情況下應該與其內置版本的返回類型兼容:邏輯運算符和關係運算符應該返回bool,算數運算符應該返回一個類類型的值,賦值運算符和複合賦值運算符則應該返回左側對象的一個引用。

6、賦值=,下表[ ], 和成員訪問箭頭->運算符必須是成員。

符合賦值運算符一般來說應該是成員但非必須,這一點與賦值運算符略有不同。解引用函數也通常是類的成員。

改變對象狀態的運算符或者與給定類型密切相關的運算符,如遞增、遞減和解引用運算符通常應該是成員。

具有對稱性的運算符可能轉換任意一段的運算對象,例如算術、相等性、關係和位運算符等,因此它們通常應該是普通的非成員函數。

7、輸出運算符儘量減少格式化操作。通常,輸出運算符應該主要負責打印對象的內容而非控制格式,輸出運算符不應該打印換行符。

8、輸入輸出運算符必須是非成員函數。一般聲明爲友元函數。

9、輸入運算符必須處理輸入可能失敗的情況,而輸出運算符不需要。我們可以等讀取了所有數據後趕在使用這些數據前一次性檢查。當讀取操作發生錯誤時,輸入運算符應該負責從錯誤中恢復。

10、算術和關係運算符的形參都是常量引用。如果定義了算數運算符則一般也會定義一個對應的複合賦值運算符,通常情況下應該使用複合賦值運算符來實現算數運算符。

11、定義了相等運算符也常常包括關係運算符。如果存在唯一一種邏輯可靠的<定義,則應該考慮爲這個類定義<運算符。如果類同時還包含==,則當且僅當<的定義和==產生的結果一致時才定義<運算符。

12、爲了與下標的原始定義兼容,下標運算符通常以所訪問的元素的引用作爲返回值,下標運算符通常有兩個版本,一個返回普通引用,另一個是類的常量成員並且返回常量引用。

13、爲了與內置版本保持一致,前置運算符應該返回遞增或遞減後對象的引用。後置版本應該返回對象的原值(遞增或遞減之前的值),返回的形式是一個值而非引用。爲了區分前置和後置運算符,後置版本額外接受一個(不被使用)int類型形參。

//class StrBlobPtr;
//前置版本
StrBlobPtr& StrBlobPtr::operator++(){
     check(curr, "increment past end of StrBlobPtr");
     ++curr;
     return *this;
}
//後置版本
StrBlobPtr StrBlobPtr::operator++(int){
   StrBlobPtr ret = *this;
   ++*this;      //使用前置版本
   return ret;
}

14、當我們重載箭頭運算符時,可以改變的是箭頭從哪個對象當中獲取成員,而箭頭獲取成員這一事實則永遠不變。必須返回類的指針或者自定義了箭頭運算符的某個類的對象。

15、如果類重載了函數調用運算符,這樣的類比起普通函數我們可以存儲狀態,使用更加靈活。該類的對象被稱作函數對象。

16、函數對象常常作爲泛型算法的實參。

17、lambda是函數對象,編譯器將一個lambda表達式翻譯成一個未命名類的未命名對象。在lambda表達式產生的類中含有一個重載的函數調用運算符。默認情況下,lambda產生的類當中的函數調用運算符是一個const成員函數。如果被聲明爲可變的,則調用運算符就不是const的了。

18、當一個lambda通過引用捕獲變量時,編譯器可以直接使用該引用,而無需在lambda產生的類中將其存儲爲成員。

當通過值捕獲變量拷貝到lambda中,lambda產生的類必須爲每個值捕獲的變量建立對應的數據成員,同時創建構造函數,令其使用捕獲的變量的值來初始化數據成員。

19、標準庫定義了一組表示算數運算符、關係運算符和邏輯運算符的類,每個類分別定義了一個執行命名操作的調用運算符。被定義在頭文件functional中。關係運算符常被用作泛型算法的謂詞。
注:關聯容器使用less<key_type>對元素排序,因此我們可以定義一個指針的set或者在map中使用指針作爲關鍵字而無需直接聲明less。

20、兩個不同類型的可調用對象卻可能共享同一種調用形式。調用形式指明瞭調用返回的類型以及傳遞給調用的實參類型。

//普通函數
int add(int i, int j) {return i + j;}
//lambda
auto mod = [](int i, int j) {return i % j;};
//函數對象類
struct divide{
    int operator()(int denominator, int divisor) {
       return denominator/divisor;
    }
};
//共享同一種調用形式
int (int, int)

21、轉換構造函數和類型轉換運算符共同定義了類類型轉換,這樣的轉換有時也被稱作用戶定義的類型轉換

22、我們不允許轉換成數組或者函數類型,但允許轉換成指針(包括數組指針、函數指針)或者引用類型。

23、儘管編譯器一次只能執行一個用戶定義的類型轉換,但是隱式的類類型轉換可以置於一個標準(內置)類型轉換之前或之後並一起使用。

//class SmallInt
//內置類型轉換將double轉化爲int
SamllInt si = 3.14;
//SmallInt的類型轉換將si轉換爲int
si + 3.14; //內置類型轉換將所得的int繼續轉換成double

24、當類類型的對象和算術類型的值之間不存在明確一對一映射關係時,儘量不要定義類型轉換運算符,我們要避免過度使用它。

25、c++11通過引入了顯示的類型轉換運算符來確保bool類型在istream中含有向bool類型轉換時不發生異常。如果表達式被用作條件,則編譯器會將顯示的類型轉換自動應用於它。

26、我們要避免有二義性的類型轉換。在兩種情況下可能發生多重轉換路徑:
(1)兩個類提供相同的類型轉換
(2)定義了多個轉換規則
因此我們要做到
(1)不要令兩個類執行相同的類型轉換:如果Foo類有一個接受Bar類對象的構造函數,則不要在Bar類中定義轉換目標是Foo類的類型轉換運算符。
(2)避免轉換目標是內置算術類型的類型轉換。特別是當你已經定義了一個轉換成算術類型的類型轉換時,接下來
——不要在定義接受算術類型的重載運算符。
——不要定義轉換到多種算術類型的類型轉換。
除了顯示地向bool類型的轉換之外,我們應該儘量避免定義類型轉換函數並儘可能地限制那些“顯然正確”的非顯示構造函數。

27、如果在調用重載函數時我們需要使用構造函數或者強制類型轉換來改變實參的類型,則這通常意味着程序的設計存在不足。

28、在調用重載函數時,如果需要額外的標準類型轉換,則該轉換的級別只有當所有可行函數都請求同一個用戶定義的類型轉換時纔有用。如果所需的用戶定義的類型轉換不止一個,則該調用具有二義性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章