本節書摘來自異步社群出版社《21天學通c++(第7版)》一書中的第12章,第12.3節,作者: 【美】siddhartha rao, 【德】nicolai m. josuttis,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。
21天學通c++(第7版)
對兩個操作數進行操作的運算符稱為雙目運算符。以全局函數或靜态成員函數的方式實作的雙目運算符的定義如下:

以類成員的方式實作的雙目運算符的定義如下:
以類成員的方式實作的雙目運算符隻接受一個參數,其原因是第二個參數通常是從類屬性獲得的。
表12.2列出了可在c++應用程式中重載或重新定義的雙目運算符。
與遞增/遞減運算符類似,如果類實作了雙目加法和雙目減法運算符,便可将其對象加上或減去指定類型的值。再來看看月曆類date,雖然前面實作了将date遞增以便前移一天的功能,但它還不支援增加5天的功能。為實作這種功能,需要實作雙目加法運算符,如程式清單12.5中的代碼所示。
程式清單12.5 實作了雙目加法運算符的月曆類
輸出:
分析:
第14~25行是雙目運算符+和-的實作,讓您能夠使用簡單的加法和減法文法,如main()中的第41和45行所示。
對字元串類來說,雙目加法運算符也很有用。第9章分析了簡單的字元串包裝類mystring,它封裝了一個c風格字元串,并提供了記憶體管理、複制等功能,如程式清單9.9所示。但這個類不支援使用如下文法将兩個字元串拼接起來:
不用說,實作運算符+後,mystring使用起來将非常容易,值得去實作它:
程式清單9.9中添加上述代碼,并提供實作為空的私有預設構造函數mystring()後,便可使用加法文法了。本章後面的程式清單12.12提供了一個mystring類,它實作了+等運算符。
運算符提高了類的可用性,但實作的運算符必須合理。對于date類,您實作了加法和減法運算符,但對于mystring類,隻實作了加法運算符(+)。這是因為對字元串執行減法運算的可能性極少,實作這樣的運算符很可能是在浪費時間。
12.3.3 實作運算符+=與-=
加并指派運算符支援文法a+=b;,這讓程式員可将對象a增加b。這樣,程式員可重載加并指派運算符,使其接受不同類型的參數b。程式清單12.6讓您能夠給date對象加上一個整數。
程式清單12.6 定義運算符+=和-=,以便将月曆向前或向後翻整型輸入參數指定的天數
運算符+=和-=是在第14~24行定義的。這些運算符讓您能夠加上或減去指定的天數,如main()中的下述代碼所示:
運算符+=和-=接受一個int參數,讓您能夠給date對象加上或減去指定的天數,就像處理的是整數一樣。您還可提供運算符+=的重載版本,讓它接受一個虛構的cdays對象作為參數:
乘并指派運算符(*=)、除并指派運算符(/=)、求模并指派運算符(%=)、減并指派運算符(-=)、左移并指派運算符(<<=)、右移并指派運算符(>>=)、異或并指派運算符(^=)、按位或并指派運算符(|=)以及按位與并指派運算符(&=)的文法都與程式清單12.6所示的加并指派運算符類似。
雖然重載運算符的最終目标是讓類更直覺,更易于使用,但很多時候實作這些運算符并沒有意義。例如,前面的月曆類date絕對不會用到按位與并指派運算符&=。這個類的使用者應該不會想通過greatday &= 20;等操作獲得有用的結果。
如果像下面這樣将兩個date對象進行比較,結果将如何呢?
由于還沒有定義等于運算符,編譯器将對這兩個對象進行二進制比較,并僅當它們完全相同時才傳回true。在有些情況下(包括現在的date類),這是可行的。然而,如果類有一個非靜态字元串成員,它包含字元串值(char *),如程式清單9.9所示的mystring,則比較結果可能不符合預期。在這種情況下,對成員屬性進行二進制比較時,實際上将比較字元串指針,而字元串指針并不相等(即使指向的内容相同),是以總是傳回false。
是以,正确的做法是定義比較運算符。等于運算符的通用實作如下:
實作不等運算符時,可重用等于運算符:
不等運算符的結果與等于運算符相反(邏輯非)。程式清單12.7列出了月曆類date定義的比較運算符。
程式清單12.7 運算符==和!=
等于運算符(==)的實作很簡單,它在年、月、日都相同時傳回true,如第14~19行所示。實作不等運算符時,重用了等于運算符的代碼,如第23行所示。有了這兩個運算符後,就可對兩個date對象(holiday1和holiday2)進行比較了,如main()中的第42和47行所示。
程式清單12.7所示的代碼讓date類足夠聰明,能夠判斷兩個date對象是否相等。然而,如果要使用該類執行類似下面的條件檢查,該如何辦呢?
如果能夠使用這個月曆類來比較兩個日期,确定哪個在前、哪個在後,将很有用。編寫這類的程式員應實作這種比較,讓這個類對使用者來說盡可能友好和直覺,如程式清單12.8所示。
程式清單12.8 實作運算符<、>、<=和>=
這裡要讨論的運算符是在第21~52行實作的。注意到實作這些運算符時,重用了其他運算符的代碼。
在main()函數的第75~84行,使用了這些運算符,以示範這些運算符使得使用date類簡單而直覺。
有時候,需要将一個類執行個體的内容賦給另一個類執行個體,如下所示:
如果您沒有提供複制指派運算符,這将調用編譯器自動給類添加的預設複制指派運算符。根據類的特征,預設複制指派運算符可能不可行,具體地說是它不複制類管理的資源。與複制構造函數一樣,為確定進行深複制,您需要提供複制指派運算符:
如果類封裝了原始指針,如程式清單9.9所示的mystring類,則確定進行深複制很重要。如果沒有實作指派運算符,編譯器将提供預設的複制指派運算符,但它隻複制char* buffer包含的位址,而不複制指向的記憶體中的内容。這與沒有提供複制構造函數時出現的情況相同。為確定指派時進行深複制,應定義複制指派運算符,如程式清單12.9所示。
程式清單12.9 對程式清單9.9所示的mystring類進行改進,添加了複制指派運算符
在這個示例中,筆者故意省略了複制構造函數,旨在減少代碼行(但您編寫這樣的類時,應添加它,詳情請參閱程式清單9.9)。複制指派運算符是在第25~39行實作的,其功能與複制構造函數很像。它首先檢查源和目标是否同一個對象。如果不是,則釋放成員buffer占用的記憶體,再重新給它配置設定足以存儲複制源中文本的記憶體,然後使用strcpy()進行複制,如第36行所示。
相比于程式清單9.9,程式清單12.9的另一個細微差别在于,使用傳回const char*的轉換運算符替代了函數getstring(),如第53~56行所示。該運算符讓mystring類使用起來更容易,如第68行所示——使用一條cout語句顯示了兩個mystring執行個體的内容。
如果您編寫的類管理着動态配置設定的資源(如c風格字元串char*)、動态數組等,除構造函數和析構函數外,請務必實作複制構造函數和複制指派運算符。
如果沒有考慮對象被複制時出現的資源所有權問題,您的類就是不完整的,使用時甚至會有危險。
要建立不允許複制的類,可将複制構造函數和複制指派運算符都聲明為私有的。隻需這樣聲明(甚至都不用提供實作)就足以讓編譯器在遇到試圖複制對象(将對象按值傳遞給函數或将一個對象賦給另一個對象)的代碼時引發錯誤。
下标運算符讓您能夠像通路數組那樣通路類,其典型文法如下:
編寫封裝了動态數組的類(如封裝了char* buffer的mystring)時,通過實作下标運算符,可輕松地随機通路緩沖區中的各個字元:
程式清單12.10是一個簡單的示例,示範了下标運算符([])讓使用者能夠使用正常數組文法來周遊mystring執行個體包含的字元。
程式清單12.10 在mystring類中實作下标運算符,以便随機通路mystring::buffer包含的字元
這個程式很有趣,它接受使用者輸入的句子,并使用它建立一個mystring對象,如第61行所示;接下來,在一個for循環中,使用下标運算符([])和數組文法逐字元地列印該字元串,如第64~65行所示。下标運算符([])是在第31~35行實作的,它首先確定指定的位置沒有超出char*buffer末尾,然後傳回指定位置處的字元。
實作運算符時,應使用關鍵字const,這很重要。在程式清單12.10中,将下标運算符([])的傳回類型聲明成了const char&。即便沒有關鍵字const,該程式也能通過編譯。這裡使用它旨在禁止使用下面這樣的代碼:
通過使用const,可禁止從外部通過運算符[]直接修改成員mystring::buffer。除将傳回類型聲明為const外,還将該運算符的函數類型設定成為const,這将禁止該運算符修改類的成員屬性。
一般而言,應盡可能使用const,以免無意間修改資料,并最大限度地保護類的成員屬性。
實作下标運算符時,可在程式清單12.10所示版本的基礎上進行改進。這個版本隻實作了一個下标運算符,它可用于讀寫動态數組的元素。
然而,也可實作兩個下标運算符,其中一個為const函數,另一個為非const函數:
編譯器很聰明,能夠在讀取mystring對象時調用const函數,而在對mystring執行寫入操作時調用非const函數。是以,如果願意,可在兩個下标函數中實作不同的功能。例如,一個運算符記錄對容器的寫入操作,而另一個記錄對容器的讀取操作。還有其他雙目運算符可被重定義或重載(如表12.2所示),但本章不打算介紹它們。這些運算符的實作與已讨論的運算符類似。
如果其他運算符(如邏輯運算符和按位運算符)有助于改善您編寫的類,就應實作它們。顯然,諸如date等月曆類沒有必要實作邏輯運算符,但處理字元串和數字的類可能需要實作它們。
應根據類的目标和用途重載運算符或實作新的運算符。
本文僅用于學習和交流目的,不代表異步社群觀點。非商業轉載請注明作譯者、出處,并保留本文的原始連結。