C 中的操作符

2022-03-15 16:58:03 字數 2511 閱讀 7560

本想部落格以每週一篇的速度更新,卻未曾料到最近幾周忙到了沒有時間坐下來寫點東西的程度。

而這一篇,也因為寫得較為匆忙,望您指出疏漏之處。

至於本文參考,可能部分來自於ec中的某個條款,並適當地加以補充。

在c++中,為基本型別定義操作符是最常見的任務。例如為乙個自定義型別提供比較操作符,以允許其作為stl容器set的元素型別。在本文中,我們不討論定義操作符的各種語法,而是簡單介紹定義操作符過程中需要注意的一些問題。

首先需要明確的就是操作符的好處。相較於成員函式,操作符擁有更強的語義特徵:一般情況下,乙個操作符常常具有固定的意義。根據該固定意義為型別定義操作符可以使操作邏輯更為清晰。舉例來說,表示4*4矩陣的matrix44類可以通過定義乘法操作符以完成對矩陣乘法的支援,而不是通過multiply()函式。另外,操作符的輸入較成員函式更為方便,也使得**更為簡潔。

眾多成熟類庫都非常重視操作符的使用,如boost或stl。這種重視同時體現在類庫內建型別和對使用者自定義型別的要求這方面。大家最為熟悉的智慧型指標以及stl容器就是非常典型的例子:智慧型指標重寫了常用的指標操作符->及*,以使對它們的使用更像乙個原生指標;而各個容器則常常需要容器所承載的型別提供特定的操作符,如set容器要求其所承載的資料型別提供比較操作符。這樣模板等技術可以在特定操作符具有固定意義的假設下更好地操作各個型別,顯著地增強模板**的通用性。

同時,自定義操作符也不應被亂用。軟體開發人員應當保證對操作符的過載保持其原有語意,否則對操作符的過載便失去了其積極意義,反而會導致誤用。

在確保存在於某個自定義型別上的特定操作符擁有積極意義後,軟體開發人員就可以開始著手實現該操作符。在實現之前,軟體開發人員應考慮以下一些問題:操作符是否應作為型別的成員函式?如果不是,全域性操作符應定義在**?是否需要使用explicit防止隱式呼叫?這些都是影響自定義操作符介面的一些因素,並在發生更改時可能影響使用者**。

首先討論操作符是否應作為型別的成員函式。一旦乙個操作符被定義為型別的成員函式,那麼它將只能成為操作符的左值。此時軟體開發人員就需要考慮將例項置於操作符左邊是否恰當。判斷是否恰當的乙個準則就是操作符的使用是否遵循了一定的習慣,而這些習慣的形成則常常與操作符的結合順序以及操作符的串聯使用有關。如在型別x實現了操作符《的情況下,軟體開發人員就可以按照下列方式使用操作符:x << cout。而《操作符是乙個左結合操作符,那麼x1 << x2 << cout將首先計算x1與x2的《運算,而不是預期的x2 << cout。因此,《操作符常需要被實現為非成員函式。

如果乙個操作符需要對應的自定義型別作為操作符的右值,那麼軟體開發人員需要定義乙個全域性操作符,並在需要的情況下將其定義為型別的友元。該全域性操作符的第二個引數需要是使用該操作符的自定義型別。

有時,操作符需要直接更改原資料,以提供更高的操作符執行效能。這種操作符常常作為型別的成員,並返回引用。最常見的例子就是+=等眾多操作自身的操作符。

總結起來,軟體開發人員通常可以使用以下規律判斷乙個操作符是否應實現為成員操作符:

1)       一元操作符應為成員。

2)       =、()、和->都應實現為成員。

3)       由於需要更改內部狀態,因此擁有賦值功能的各個操作符(+=、<<=、|=等)都必須是成員操作符。

4)       其它所有二元操作符都應實現為非成員操作符。

其中有幾個特殊的操作符:.、?:、::以及*都不能過載。

如果決定將乙個操作符定義為非成員函式,那麼軟體開發人員需要考慮該操作符定義所在的範圍。一般情況下,該操作符的定義應當與該自定義型別處於同乙個命名空間內。只有在這種情況下,不同命名空間的**才能在adl的輔助下正確查詢到該操作符。

另乙個需要討論的問題則是操作符是否可以被隱式呼叫?需要考慮這個問題的常常是型別轉換操作符。在操作符沒有被explicit修飾的情況下,編譯器可能在多種情況下自作主張地呼叫型別轉化操作符,如在匹配函式呼叫的引數時。通過在操作符前使用explicit關鍵字,軟體開發人員可以禁止編譯器對型別轉化操作符的呼叫。需要讀者注意的是,在操作符前使用explicit關鍵字這種用法需要啟用visual studio所提供的clr擴充套件支援。

需要提到的是,型別轉換操作符是操作符過載中的乙個較為特殊的情況:其與建構函式和析構函式一樣,遵守無返回型別的規定。

另乙個與型別轉換操作符有關的比較隱蔽的問題則是編譯時其是否可以被隱式呼叫。假設型別test定義了乙個轉化為testclass的型別轉換操作符,並且型別testclass按照成員的方式定義了乙個以int作為右值的加法操作符。那麼對於test型別的變數test,表示式test + 4將導致乙個編輯錯誤。而如果軟體開發人員將該操作符實現為全域性操作符,那麼編譯器將順利地完成編譯。這是因為編譯器在處理這兩種情況時使用的是不同的機制:函式呼叫過載以及引數匹配。簡單地說,編譯器並不支援成員操作符查詢時的隱式型別轉化,而在使用全域性操作符的時候,編譯器將自動為引數查詢轉換操作符完成轉換。

一旦確定了操作符的簽名,軟體開發人員就可以根據操作符所需要執行的實際功能完成對操作符邏輯的實現。在實現中常需要考慮的是操作符的效能:如果操作符被非常頻繁地呼叫,那麼該操作符的執行效能將是乙個需要考慮的重要因素。一般來說,軟體開發人員需要考慮的效能優化方式主要有內聯以及nrv優化兩種。內聯一般適合具有較簡單執行邏輯的操作符實現,而nrv優化則較為適合型別例項消耗較大的情況。

C 中的操作符

c 的精彩世界還離不開其提供的豐富的操作符,按照運算元的個數,c 操作符可分為以下幾類 如果從操作符的作用來看,c 操作符可以分為賦值操作符,算術操作符,關係操作符,邏輯操作符,位操作符和其它操作符。1.賦值操作符 賦值操作符除基本的賦值操作符 之外還包括以下的組合賦值操作符 要注意的是,對於復合的...

c語言操作符 位操作符 移位操作符

1 按位操作符 1.1 按位 與 雙目運算子 僅當兩個運算元都為1時,結果為1,否則為0。參與運算的數以補碼方式出現。例 9 5 1 0000 1001 9的補碼 0000 0101 5的補碼 0000 0001 1的補碼 應用 a 通常將某些位清零或保留某些位。例如 將a的高八位清零,保留低八位,...

ruby中的 操作符和 操作符

url str foo foo str bar foobar str foo foo str.object id 606274188 str bar foobar str.object id 606283808 str foobar foobarfoobar str.object id 606283...