c++ primer 第十四章過載運算與型別轉換
c++ primer 第十四章過載運算與型別轉換
14.1 基本概念
過載運算子引數數量與運算子的運算物件數量一樣多。除了operator()之外其它過載運算子不能含有預設實參。
如果一個運算子是成員函式,第一個運算物件隱式繫結為this指標。
運算子函式或者是成員函式,或者引數至少有一個類型別。
可以過載大多數運算子但不能發明新的。優先順序和結合律不變。
某些運算子不應該被過載,如邏輯與邏輯或和逗號運算子、取地址運算子等等。
過載的運算子應該與原運算子操作類似。如果有算數運算子或者位運算子,一般也提供複合賦值運算子。
作為成員函式時左側運算物件必須是運算子所屬類的一個物件。
14.2 輸入和輸出運算子
14.2.1 過載輸出運算子<<
第一個形參是ostream物件的引用,第二個一般是常量的引用。返回ostream形參。
儘量減少格式化操作如換行。過載輸入輸出必須是非成員函式,因為輸入輸出的類是標準庫型別。
14.2.2 過載輸入運算子>>
第一個形參是istream物件的引用,第二個是個非常量的引用,返回istream形參。
輸入運算子必須考慮輸入可能失敗的情況。
14.3 算術和關係運算符
如果定義了算術運算子,一般也會定義相應的複合賦值運算子。此時最有效的方式是使用複合賦值來定義算術運算子。
14.3.1 相等運算子
相等運算與不相等運算互相依賴,一個可以委託給另一個。
14.3.2 關係運算符
- 定義順序關係,令其與關聯容器中對關鍵字的要求一致。
- 如果類同時含有== 運算子,那麼定義一種關係與 ==保持一致。特別是兩個物件是!=的,那麼一個物件應該<另外一個。
14.4 賦值運算子
不同於拷貝賦值和移動賦值運算子,普通賦值運算子可以接收別的型別作為右側運算物件。返回左側運算物件的引用。
通常把包括複合賦值在內的賦值運算都定義在類的內部。
14.5 下標運算子
下標運算子必須是成員函式。返回所訪問元素的引用,一般需要定義常量版本和非常量版本。
14.6 遞增和遞減運算子
遞增遞減運算子定義為類的成員。前置運算子返回遞增或遞減後物件的引用。
使用operator++(int)表示後置遞增,即新增一個不會使用的int引數。
使用前置版本來完成後置版本的操作。如果要顯示呼叫後置版本,傳遞一個整數引數。
14.7 成員訪問運算子
箭頭運算子(->)必須是類的成員,解引用運算子(*)一般也是。定義為const成員。
箭頭運算子必須是獲取成員的操作。
根據point型別的不同,point->mem分別等價於:
- (*point).mem; //point 是一個內建的指標型別
- point.operator()->mem; //point是類 的一個物件
point->mem執行過程如下:
- 如果point是指標,那麼表示式等價於(*point).mem。
- 如果point是定義了operator->的類的一個物件,那麼使用point.operator->()的結果來獲取mem。如果該結果是個指標執行第一步,如果結果也含有->成員函式,重複此步驟知道返回結果或出錯。
14.8 函式呼叫運算子
如果類定義了呼叫運算子,則該類的物件成為函式物件。
函式物件常常作為泛型演算法的實參,而且與函式不同可以含有內部狀態。
14.8.1 lambda是函式物件
當我們編寫一個lambda後,編譯器將該表示式翻譯為一個未命名類的未命名物件。
預設情況下lambda不能改變它捕獲的變數,因此由它產生的類的函式呼叫運算子是一個const成員函式。
lambda表示式產生的類不含預設建構函式、賦值運算子及預設解構函式;是否含有預設的拷貝/移動建構函式通常視捕獲的資料成員型別而定。
14.8.2 標準庫定義的函式物件
標準庫定義了一組表示算術運算子、關係運算符和邏輯運算子的類。
表示運算子的函式物件類常用來替換演算法中的預設運算子。
14.8.3 可呼叫物件與function
C++中有幾種可呼叫物件:函式、函式指標、lambda表示式、bind建立的物件以及過載了函式呼叫運算子的類。
不同型別的可呼叫物件可能共享同一種呼叫形式。比如int(int, int)。
標準庫function,定義在functional標頭檔案中,可以將同一種呼叫形式的不同可呼叫物件作為一個型別的物件。如function<int(int,int)>,可以使用map<string, function<int(int,int)>>將不同型別的可呼叫物件放到一個map中。
不能直接將過載函式的名字放入function型別的物件中,可以使用函式指標或者lambda來解決二義性。
14.9 過載、型別轉換與運算子
轉換建構函式和型別轉換運算子共同定義了類型別轉換,有時也成為使用者定義的型別轉換。
14.9.1 型別轉換運算子
形式如:operator type() const; type為轉換型別。轉換到的型別需要能夠作為函式的返回型別。不允許轉換為陣列或者函式型別,但允許轉換為指標或者引用型別。
型別轉換運算子可能會產生意料之外的計算結果。如istream可以轉換為bool型的話那麼 cin<<i 可以轉換為數字0或1的左移位。
現實的型別轉換運算子可以防止這樣的異常。不過當表示式用作條件時,會將顯示的型別轉換也隱式轉換為bool型。
因為在條件判斷是會自動轉換為bool型,所以bool型轉換一般定義為顯示轉換。
14.9.2 避免有二義性的型別轉換
如果類中包含一個或多個型別轉換,則必須確保在類型別和目標型別之間只存在唯一一種轉換方式。有兩種情況會導致多重轉換路徑:
- 兩個類提供相同的型別轉換。如A類接收B作為引數有個轉換建構函式,而B類有轉換為A的型別轉換運算子。
- 類定義了多個轉換規則。比如算術運算子相關的轉換規則。
如果類定義了一組型別轉換,它們的轉換源或者轉換目標型別本身可以通過其它型別轉換聯絡到一起,也會產生二義性問題。如類中定義了多個引數都是算術型別的建構函式,或者轉換目標都是算術型別的型別轉換符。
如果兩個或多個型別轉換都提供了同一種可行匹配,則這些型別轉換一樣好。
如果兩個或多個使用者定義的型別轉換都提供了可行匹配,那麼認為這些型別轉換一樣好。轉換級別順序只有呼叫同一個使用者定義的型別轉換時才有用。
14.9.3 函式匹配與過載運算子
過載的運算子也是過載的函式。
和普通函式呼叫不同,我們無法通過呼叫形式區分當前呼叫的是成員函式還是非成員函式。因此在函式匹配時兩者都需要考慮。
如果對同一個類既提供了轉換目標是算術型別的型別轉換,也提供了過載的運算子,那麼會遇到過載運算子與內建運算子的二義性問題。