c++primer——過載運算與型別轉換

TinnCHEN發表於2019-04-21

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、在呼叫過載函式時,如果需要額外的標準型別轉換,則該轉換的級別只有當所有可行函式都請求同一個使用者定義的型別轉換時才有用。如果所需的使用者定義的型別轉換不止一個,則該呼叫具有二義性。

相關文章