重载操作符与转换(上)
重載操作符的作用:
通過操作符重載,程序員能夠針對類類型的操作數定義不同的操作符版本。程序用移位操作符(>> 和 <<)進行輸入輸出,用加號操作符(+)將兩個 Sales_items 相加。
通過操作符重載,可以定義大多數操作符,使它們用于類類型對象。明智地使用操作符重載可以使類類型的使用像內置類型一樣直觀。標準庫為容器類定義了幾個重載操作符。這些容器類定義了下標操作符以訪問數據元素,定義了 * 和 -> 對容器迭代器解引用。這些標準庫的類型具有相同的操作符,使用它們就像使用內置數組和指針一樣。允許程序使用表達式而不是命名函數,可以使編寫和閱讀程序容易得多。
cout << "The sum of " << v1 << " and " << v2<< " is " << v1 + v2 << endl;和以下更為冗長的代碼相比較就能夠看到。如果 IO 使用命名函數,類似下面的代碼將無法避免:
// hypothetical expression if IO used named functionscout.print("The sum of ").print(v1).print(" and ").print(v2).print(" is ").print(v1 + v2).print("\n").flush();重載操作符是具有特殊名稱的函數:保留字 operator 后接需定義的操作符號。像任意其他函數一樣,重載操作符具有返回類型和形參表,如下語句:
Sales_item operator+(const Sales_item&, const Sales_item&);聲明了加號操作符,可用于將兩個 Sales_item 對象“相加”并獲得一個 Sales_item 對象的副本。除了函數調用操作符之外,重載操作符的形參數目(包括成員函數的隱式 this 指針)與操作符的操作數數目相同。函數調用操作符可以接受任意數目的操作數。
可重載的操作符:
不可重載的操作符:
通過連接其他合法符號可以創建新的操作符。例如,定義一個 operator** 以提供求冪運算是合法的。
- 重載操作符必須有一個類類型操作數
?用于內置類型的操作符,其含義不能改變。例如,內置的整型加號操作符不能重定義:
// error: cannot redefine built-in operator for intsint operator+(int, int);也不能為內置數據類型重定義加號操作符。例如,不能定義接受兩個數組類型操作數的 operator+。重載操作符必須具有至少一個類類型或枚舉類型的操作數。這條規則強制重載操作符不能重新定義用于內置類型對象的操作符的含義。
- 優先級和結合性是固定的
操作符的優先級、結合性或操作數目不能改變。不管操作數的類型和操作符的功能定義如何,表達式
x == y +z;總是將實參 y 和 z 綁定到 operator+,并且將結果用作 operator== 右操作數。有四個符號(+, -, * 和 &)既可作一元操作符又可作二元操作符,這些操作符有的在其中一種情況下可以重載,有的兩種都可以,定義的是哪個操作符由操作數數目控制。除了函數調用操作符 operator() 之外,重載操作符時使用默認實參是非法的。
- 不具備短路求值特性(作為"&&"和"||"操作符的操作數表達式,這些表達式在進行求值時,只要最終的結果已經可以確定是真或假,求值過程便告終止,這稱之為短路求值)
重載操作符并不保證操作數的求值順序,尤其是,不會保證內置邏輯 AND、邏輯 OR和逗號操作符的操作數求值。在 && 和 || 的重載版本中,兩個操作數都要進行求值,而且對操作數的求值順序不做規定。因此,重載 &&、|| 或逗號操作符不是一種好的做法。
- 類成員與非成員
大多數重載操作符可以定義為普通非成員函數或類的成員函數。作為類成員的重載函數,其形參看起來比操作數數目少 1。作為成員函數的操作符有一個隱含的 this 形參,限定為第一個操作數。重載一元操作符如果作為成員函數就沒有(顯式)形參,如果作為非成員函數就有一個形參。類似地,重載二元操作符定義為成員時有一個形參,定義為非成員函數時有兩個形參。類 Sales_item 中給出了成員和非成員二元操作符的良好例子。我們知道該類有一個加號操作符。因為它有一個加號操作符,所以也應該定義一個復合賦值(+=)操作符,該操作符將一個 Sales_item 對象的值加至另一個 Sales_item 對象。
一般將算術和關系操作符定義非成員函數,而將賦值操作符定義為成員:
// member binary operator: left-hand operand bound to implicit this pointerSales_item& Sales_item::operator+=(const Sales_item&);// nonmember binary operator: must declare a parameter for each operandSales_item operator+(const Sales_item&, const Sales_item&);加和復合賦值都是二元操作符,但這些函數定義了不同數目的形參,差異的原因在于 this 指針。當操作符為成員函數,this 指向左操作數,因此,非成員 operator+ 定義兩個形參,都引用 const Sales_item 對象。即使復合賦值是二元操作符,成員復合賦值操作符也只接受一個(顯式的)形參。使用操作符時,一個指向左操作數的指針自動綁定到 this,而右操作符限定為函數的唯一形參。復合賦值返回一個引用而加操作符返回一個 Sales_item 對象,這也沒什么。當應用于算術類型時,這一區別與操作符的返回類型相匹配:加返回一個右值,而復合賦值返回對左操作數的引用。
- 操作符重載和友元關系
操作符定義為非成員函數時,通常必須將它們設置為所操作類的友元。在這種情況下,操作符通常需要訪問類的私有部分。Sales_item 類也是說明為何有些操作符需要設置為友元的一個好例子。它定義了一個成員操作符,并且有三個非成員操作符。有兩個非成員操作符需要訪問私有數據成員,需聲明為友元:
class Sales_item {friend std::istream& operator>>(std::istream&, Sales_item&);friend std::ostream& operator<<(std::ostream&, const Sales_item&);public:Sales_item& operator+=(const Sales_item&);};Sales_item operator+(const Sales_item&, const Sales_item&);輸入和輸出操作符需要訪問 private 數據不會令人驚訝,畢竟,它們的作用是讀入和寫出那些成員。另一方面,不需要將加操作符設置為友元,它可以用 public 成員 operator+= 實現。
- 使用重載操作符
使用重載操作符的方式,與內置類型操作數上使用操作符的方式一樣。假定 item1 和 item2 是 Sales_item 對象,可以打印它們的和,就像打印兩個 int 的和一樣:
cout << item1 + item2 << endl;這個表達式隱式調用為 Sales_items 類而定義的 operator+。也可以像調用普通函數一樣調用重載操作符函數,指定函數并傳遞適當類型適當數目的形參:
// equivalent direct call to nonmember operator functioncout << operator+(item1, item2) << endl;這個調用與 item1 和 item2 相加的表達式等效。
調用成員操作符函數與調用任意其他函數是一樣的:指定運行函數的對象,然后使用點或箭頭操作符獲取希望調用的函數,同時傳遞所需數目和類型的實參。對于二元成員操作符函數的情況,我們必須傳遞一個操作數:
item1 += item2; // expression based "call"item1.operator+=(item2); // equivalent call to member operator function兩個語句都將 item2 的值加至 item1。第一種情況下,使用表達式語法隱式調用重載操作符函數:第二種情況下,在 item1 對象上調用成員操作符函數。
#include <iostream> #include <string> using namespace std;class Sales_item { public:Sales_item() :product_name("UnNamed"), product_price(0){}Sales_item(string spn, int spp) :product_name(spn), product_price(spp){}Sales_item& operator+=(const Sales_item&); private:string product_name;int product_price;friend std::istream& operator>>(std::istream&, Sales_item&);friend std::ostream& operator<<(std::ostream&, const Sales_item&); }; Sales_item& Sales_item::operator+=(const Sales_item&si) {product_price += si.product_price;return *this; }Sales_item operator+(const Sales_item&si1, const Sales_item&si2) {/*int totalprice = si1.product_price + si2.product_price;*///非成員函數不能訪問privateSales_item si("Cakes", 0);si += si1;si += si2;return si; }std::ostream& operator<<(std::ostream&out, const Sales_item&si){out << si.product_name << ":" << si.product_price << endl;return out;//保證可以連續輸出 }std::istream& operator>>(std::istream&in, Sales_item&si){cout << "input price of "<<si.product_name << endl;in >> si.product_price;return in; }int main() {Sales_item si("Cake",15);Sales_item si2("Cake", 16);Sales_item si3("Cake", 17);Sales_item si4("Cake", 18);cout << si << endl;cin >> si;cout << si << endl;si += si2;cout << si << endl;cout << si3 + si4 << endl;system("pause");return 0; }輸出結果:
- 重載操作符的設計
不要重載具有內置含義的操作符。賦值操作符、取地址操作符和逗號操作符對類類型操作數有默認含義。如果沒有特定重載版本,編譯器就自己定義以下這些操作符。//重載操作符的一個原因就是這個操作符對類類型沒有操作。比如說“+”,把兩個類相加,加號不知道該如何操作,所以需要給這兩個類重載加號操作符。但是有些操作符已經有了對類的操作,這時就不必再給這個操作符定義類的操作了。
合成賦值操作符進行逐個成員賦值:使用成員自己的賦值:使用成員自己的賦值操作依次對每個成員進行賦值;默認情況下,取地址操作符(&)和逗號操作符(,)在類類型對象上的執行,與在內置類型對象上的執行一樣。取地址操作符返回對象的內存地址,逗號操作符從左至右計算每個表達式的值,并返回最右邊操作數的值;內置邏輯與(&&)和邏輯或(||)操作符使用短路求值。如果重新定義該操作符,將失去操作符的短路求值特征。
通過為給定類類型的操作數重定義操作符,可以改變這些操作符的含義。重載逗號、取地址、邏輯與、邏輯或等等操作符通常不是好做法。這些操作符具有有用的內置含義,如果我們定義了自己的版本,就不能再使用這些內置含義。
有時我們需要定義自己的賦值運算。這樣做時,它應表現得類似于合成操作符:賦值之后,左右操作數的值應是相同的,并且操作符應返回對左操作數的引用。重載的賦值運算應在賦值的內置含義基礎上進行定制,而不是完全繞開。
大多數操作符對類對象沒有意義。為類設計操作符,最好的方式是首先設計類的公用接口。定義了接口之后,就可以考慮應將哪些操作符定義為重載操作符。那些邏輯上可以映射到某個操作符的操作可以考慮作為候選的重載操作符。例如:相等測試操作應使用 operator==;一般通過重載移位操作符進行輸入和輸出;測試對象是否為空的操作可用邏輯非操作符 operator! 表示。
復合賦值操作符。如果一個類有算術操作符或位操作符,那么,提供相應的復合賦值操作符一般是個好的做法。例如,Sales_item 類定義了 + 操作符,邏輯上,它也應該定義 +=。不用說,操作符的行為應定義為與內置操作符一樣:復合賦值的行為應與 + 之后接著 = 類似。
相等和關系操作符。將要用作關聯容器鍵類型的類應定義 < 操作符。關聯容器默認使用鍵類型的 < 操作符。即使該類型將只存儲在順序容器中,類通常也應該定義相等(==)和小于(<)操作符,理由是許多算法假定這個操作符存在。例如 sort 算法使用 < 操作符,而 find 算法使用 == 操作符。
如果類定義了相等操作符,它也應該定義不等操作符 !=。類用戶會假設如果可以進行相等比較,則也可以進行不等比較。同樣的規則也應用于其他關系操作符。如果類定義了 <,則它可能應該定義全部的四個關系操作符(>,>=,<,<=)。
- 選擇成員或非成員實現
為類設計重載操作符的時候,必須選擇是將操作符設置為類成員還是普通非成員函數。在某些情況下,程序員沒有選擇,操作符必須是成員;在另一些情況下,有些經驗原則可指導我們做出決定。下面是一些指導原則,有助于決定將操作符設置為類成員還是普通非成員函數:
賦值(=)、下標([])、調用(())和成員訪問箭頭(->)等操作符必須定義為成員,將這些操作符定義為非成員函數將在編譯時標記為錯誤;像賦值一樣,復合賦值操作符通常應定義為類的成員,與賦值不同的是,不一定非得這樣做,如果定義非成員復合賦值操作符,不會出現編譯錯誤;改變對象狀態或與給定類型緊密聯系的其他一些操作符,如自增、自減和解引用,通常就定義為類成員;//自增自減是為了修改自身,所以必須有對自身操作的權限,那么設為成員函數就可以修改自身的private成員,這樣設計師最好的。對稱的操作符,如算術操作符、相等操作符、關系操作符和位操作符,最好定義為普通非成員函數。
- 輸出操作符重載
為了與 IO 標準庫一致,操作符應接受 ostream& 作為第一個形參,對類類型 const 對象的引用作為第二個形參,并返回對 ostream 形參的引用。重載輸出操作符一般的簡單定義如下:
// general skeleton of the overloaded output operatorostream&operator <<(ostream& os, const ClassType &object){// any special logic to prepare object// actual output of membersos << // ...// return ostream objectreturn os;}第一個形參是對 ostream 對象的引用,在該對象上將產生輸出。ostream 為非 const,因為寫入到流會改變流的狀態。該形參是一個引用,因為不能復制 ostream 對象。第二個形參一般應是對要輸出的類類型的引用。該形參是一個引用以避免復制實參。它可以是 const,因為(一般而言)輸出一個對象不應該改變對象。使形參成為 const 引用,就可以使用同一個定義來輸出 const 和非 const 對象。返回類型是一個 ostream 引用,它的值通常是輸出操作符所操作的 ostream 對象。
ostream&operator<<(ostream& out, const Sales_item& s){out << s.isbn << "\t" << s.units_sold << "\t"<< s.revenue << "\t" << s.avg_price();return out;}輸出 Sales_item,就需要輸出它的三個數據成員以及計算得到的平均銷售價格,每個成員用制表符間隔。輸出值之后,該操作符返回對所寫 ostream 對象的引用。
輸出操作符格式化盡量少。關于輸出,類設計者面臨一個重要決定:是否格式化以及進行多少格式化。一般而言,輸出操作符應輸出對象的內容,進行最小限度的格式化,它們不應該輸出換行符。
- IO操作符必須為非成員函數
我們不能將該操作符定義為類的成員,否則,左操作數將只能是該類類型的對象:
// if operator<< is a member of Sales_item Sales_item item;item << cout;//因為成員函數是類對象發起調用,所以對象會寫到前面。如果想要支持正常用法,則左操作數必須為 ostream 類型。這意味著,如果該操作符是類的成員,則它必須是 ostream 類的成員,然而,ostream 類是標準庫的組成部分,我們(以及任何想要定義 IO 操作符的人)是不能為標準庫中的類增加成員的。
- 輸入操作符>>的重載
與輸出操作符類似,輸入操作符的第一個形參是一個引用,指向它要讀的流,并且返回的也是對同一個流的引用。它的第二個形參是對要讀入的對象的非 const 引用,該形參必須為非 const,因為輸入操作符的目的是將數據讀到這個對象中。更重要但通常重視不夠的是,輸入和輸出操作符有如下區別:輸入操作符必須處理錯誤和文件結束的可能性。
istream&operator>>(istream& in, Sales_item& s){double price;in >> s.isbn >> s.units_sold >> price;// check that the inputs succeededif (in)s.revenue = s.units_sold * price;elses = Sales_item(); // input failed: reset object to default statereturn in;}這個操作符從 istream 形參中讀取三個值:一個 string 值,存儲到 isbn 成員中;一個 unsigned 值,存儲到 Sales_item 形參的 units_sold 成員中;一個 double 值,存儲到 Sales_item 形參的 price 成員中。假定讀取成功,操作符用 price 和 units_sold 來設置 Sales_item 對象的 revenue 成員。
檢查是否發生錯誤:Sales_item 的輸入操作符將讀入所期望的值并檢查是否發生錯誤。可能發生的錯誤包括如下種類:任何讀操作都可能因為提供的值不正確而失敗。例如,讀入 isbn 之后,輸入操作符將期望下兩項是數值型數據。如果輸入非數值型數據,這次的讀入以及流的后續使用都將失敗;任何讀入都可能碰到輸入流中的文件結束或其他一些錯誤。
處理輸入錯誤:如果輸入操作符檢測到輸入失敗了,則確保對象處于可用和一致的狀態是個好做法。如果對象在錯誤發生之前已經寫入了部分信息,這樣做就特別重要。例如,在 Sales_item 的輸入操作符中,可能成功地讀入了一個新的 isbn,然后遇到流錯誤。在讀入 isbn 之后發生錯誤意味著舊對象的 units_sold 和 revenue 成員沒變,結果會將另一個 isbn 與那個數據關聯。在這個操作符中,如果發生了錯誤,就將形參恢復為空 Sales_item 對象,以避免給它一個無效狀態。用戶如果需要輸入是否成功,可以測試流。即使用戶忽略了輸入可能錯誤,對象仍處于可用狀態——它的成員都已經定義。類似地,對象將不會產生令人誤解的結果——它的數據是內在一致的。設計輸入操作符時,如果可能,要確定錯誤恢復措施,這很重要。//這些都是在設計一個類時應該考慮到的細節。
- 算術操作符和關系操作符
一般而言,將算術和關系操作符定義為非成員函數,像下面給出的 Sales_item 加法操作符一樣:
// assumes that both objects refer to the same isbn Sales_itemoperator+(const Sales_item& lhs, const Sales_item& rhs){Sales_item ret(lhs); // copy lhs into a local object that we'll returnret += rhs; // add in the contents of rhsreturn ret; // return ret by value}加法操作符并不改變操作符的狀態,操作符是對 const 對象的引用;相反,它產生并返回一個新的 Sales_item 對象,該對象初始化為 lhs 的副本。我們使用 Sales_item 的復合賦值操作符來加入 rhs 的值。注意,為了與內置操作符保持一致,加法返回一個右值,而不是一個引用。算術操作符通常產生一個新值,該值是兩個操作數的計算結果,它不同于任一操作數且在一個局部變量中計算,返回對那個變量的引用是一個運行時錯誤。既定義了算術操作符又定義了相關復合賦值操作符的類,一般應使用復合賦值實現算術操作符。根據復合賦值操作符(如 +=)來實現算術操作符(如 +),比其他方式更簡單且更有效。例如,我們的 Sales_item 操作符。如果我們調用 += 來實現 +,則可以不必創建和撤銷一個臨時量來保存 + 的結果。
- ?相等操作符
通常,C++ 中的類使用相等操作符表示對象是等價的。即,它們通常比較每個數據成員,如果所有對應成員都相同,則認為兩個對象相等。與這一設計原則一致,Sales_item 的相等操作符應比較 isbn 以及銷售數據:
inline booloperator==(const Sales_item &lhs, const Sales_item &rhs){// must be made a friend of Sales_itemreturn lhs.units_sold == rhs.units_sold &&lhs.revenue == rhs.revenue &&lhs.same_isbn(rhs);}inline booloperator!=(const Sales_item &lhs, const Sales_item &rhs){return !(lhs == rhs); // != defined in terms of operator==}這些函數的定義并不重要,重要的是這些函數所包含的設計原則:
如果類定義了 == 操作符,該操作符的含義是兩個對象包含同樣的數據;如果類具有一個操作,能確定該類型的兩個對象是否相等,通常將該函數定義為 operator== 而不是創造命名函數。用戶將習慣于用 == 來比較對象,而且這樣做比記住新名字更容易;如果類定義了 operator==,它也應該定義 operator!=。用戶會期待如果可以用某個操作符,則另一個也存在;相等和不操作符一般應該相互聯系起來定義,讓一個操作符完成比較對象的實際工作,而另一個操作符只是調用前者。
?定義了 operator== 的類更容易與標準庫一起使用。有些算法,如 find,默認使用 == 操作符,如果類定義了 ==,則這些算法可以無須任何特殊處理而用于該類類型。
?
轉載于:https://www.cnblogs.com/predator-wang/p/5221156.html
總結
以上是生活随笔為你收集整理的重载操作符与转换(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: leetcode 35 Search I
- 下一篇: 将php数组存取到本地文件