4.7條件運算符
- 條件運算符(?:)允許我們把簡單的if else邏輯嵌入到單個表達式當中,條件運算符按照如下形式使用:
- cond ? expr1 : expr2;其中cond是判斷條件的表達式,而expr1和expr2是兩個類型相同或可能轉換為某個公共類型的表達式。條件運算符的執行過程是:首先求cond的值,如果條件為真對expr1求值并返回該值,否則對expr2求值并返回該值。舉個例子,我們可以使用條件運算符判斷成績是否合格:
- string finalgrade = (grade<60) ?"fail" : "pass”;
- 條件部分判斷成績是否小于60。如果小于,表達式的結果是"fail",否則結果是"pass"。有點類似于邏輯與運算符和邏輯或運算符(&&和||),條件運算符只對expr1和expr2中的一個求值。
- 當條件運算符的兩個表達式都是左值或者能轉換成同一種左值類型時,運算的結果是左值;否則運算的結果是右值。
嵌套條件運算符
- 允許在條件運算符的內部嵌套另外一個條件運算符。也就是說,條件表達式可以作為另外一個條件運算符的cond或expr
- 舉個例子,使用一對嵌套的條件運算符可以將成績分成三檔:優秀(highpass)>合格(pass)和不合格(fail):
- finalgrade=(grade>90)?"highpassn:(grade<60)?“fail":"pass";
- 第一個條件檢查成績是否在90分以上,如果是,執行符號?后面的表達式,得到"highpass";如果否,執行符號:后面的分支。這個分支本身又是一個條件表達式,它檢查成績是否在60分以下,如果是,得到"fail";否則得到"pass"。
- 條件運算符滿足右結合律,意味著運算對象(一般)按照從右向左的順序組合。因此在上面的代碼中,靠右邊的條件運算(比較成績是否小于60)構成了靠左邊的條件運算的:分支。
- 隨著條件運算嵌套層數的增加,代碼的可讀性急劇下降.因此,條件運算的嵌套最好別超過兩到三層
4.8位運算符
- 位運算符作用于整數類型的運算對象,并把運算對象看成是二進制位的集合。位運算符提供檢查和設置二進制位的功能,如17.2節(第640頁)將要介紹的,一種名為bitset的標準庫類型也可以表示任意大小的二進制位集合,所以位運算符同樣能用于bitset類型.
- 一般來說,如果運算對象是''小整型”,則它的值會被自動提升(參見4.11.1節,第142頁)成較大的整數類型。運算對象可以是帶符號的,也可以是無符號的。如果運算對象是帶符號的且它的值為負,那么位運算符如何處理運算對象的“符號位”依賴于機器。而且,此時的左移操作可能會改變符號位的值,因此是一種未定義的行為。
- 關于符號位如何處理沒有明確的規定,所以強烈建議僅將位運算符用于處理無符號類型。
移位運算符
- 之前在處理輸入和輸出操作時,我們已經使用過標準IO庫定義的<<運算符和>>運算符的重載版本。這兩種運算符的內置含義是對其運算對象執行基于二進制位的移動操作,首先令左側運算對象的內容按照右側運算對象的要求移動指定位數,然后將經過移動的(可能還進行了提升)左側運算對象的拷貝作為求值結果。其中,右側的運算對象一定不能為負,而且值必須嚴格小于結果的位數,否則就會產生未定義的行為。二進制位或者向左移(?)或者向右移(?),移出邊界之外的位就被舍棄掉了:
- 左移運算符(<<)在右側插入值為0的二進制位。右移運算符(>>)的行為則依賴于其左側運算對象的類型:如果該運算對象是無符號類型,在左側插入值為0的二進制位:
- 如果該運算對象是帶符號類型,在左側插入符號位的副本或值為0的二進制位,如何選擇要視具體環境而定。
位求反運算符
- 位求反運算符( ~) 將運算對象逐位求反后生成一個新值,將 1 置為0、將 0 置為1:
- char類型的運算對象首先提升成int類型,提升時運算對象原來的位保持不變,往高位(highorderposition)添加0即可。因此在本例中,首先將bits提升成int類型,增加24個高位0,隨后將提升后的值逐位求反。
位與、位或、位異或運算符
- 與(&)、或(|)、異或(^)運算符在兩個運算對象上逐位執行相應的邏輯操作:
- 對于位與運算符(&)來說,如果兩個運算對象的對應位置都是1則運算結果中該位為1,否則為0。對于位或運算符(|)來說,如果兩個運算對象的對應位置至少有一個為1則運算結果中該位為1,否則為0。對于位異或運算符(^)來說,如果兩個運算對象的對應位置有且只有一個為1則運算結果中該位為1,否則為0
移位運算符(又叫10運算符)滿足左結合律
- 盡管很多程序員從未直接用過位運算符,但是幾乎所有人都用過它們的重載版本來進行10操作。重載運算符的優先級和結合律都與它的內置版本一樣,因此即使程序員用不到移位運算符的內置含義,也仍然有必要理解其優先級和結合律。
- 因為移位運算符滿足左結合律,所以表達式
4.9 sizeof運算符
- sizeof運算符返回一條表達式或一個類型名字所占的字節數。sizeof運算符滿足右結合律,其所得的值是一個size_t類型(參見3.5.2節,第 103頁)的常量表達式(參 見2.4.4節,第 58頁)。運算符的運算對象有兩種形式:
- sizeof (type)
- sizeof expr
- 這些例子中最有趣的一個是sizeof *p。首先,因為sizeof滿足右結合律并且與*運算符的優先級一樣,所以表達式按照從右向左的順序組合。也就是說,它等價于sizeof(*p)
- 其次,因為sizeof不會實際求運算對象的值,所以即使p是一個無效(即未初始化)的指針(參見2.3.2節,第47頁)也不會有什么影響。在sizeof的運算對象中解引用一個無效指針仍然是一種安全的行為,因為指針實際上并沒有被真正使用。
- sizeof不需要真的解引用指針也能知道它所指對象的類型。新標準允許我們使用作用域運算符來獲取類成員的大小。通常情況下只有通過類的對象才能訪問到類的成員,但是sizeof運算符無須我們提供一個具體的對象,因為要想知道類成員的大小無須真的獲取該成員。
- sizeof運算符的結果部分地依賴于其作用的類型:
- 對char或者類型為char的表達式執行sizeof運算,結果得1。
- 對引用類型執行sizeof運算得到被引用對象所占空間的大小。
- 對指針執行sizeof運算得到指針本身所占空間的大小。
- 對解引用指針執行sizeof運算得到指針指向的對象所占空間的大小,指針不需有效。
- 對數組執行sizeof運算得到整個數組所占空間的大小,等價于對數組中所有的元素各執行一次sizeof運算并將所得結果求和。注意,sizeof運算不會把數組轉換成指針來處理。
- 對string對象或vector對象執行sizeof運算只返回該類型固定部分的大小,不會計算對象中的元素占用了多少空間。
- 因為執行sizeof運算能得到整個數組的大小,所以可以用數組的大小除以單個元素的大小得到數組中元素的個數:
?
4.10逗號運算符
- 逗號運算符含有兩個運算對象,按照從左向右的順序依次求值。和邏輯與、邏輯或以及條件運算符一樣,逗號運算符也規定了運算對象求值的順序。
- 對于逗號運算符來說,首先對左側的表達式求值,然后將求值結果丟棄掉。逗號運算符真正的結果是右側表達式的值。如果右側運算對象是左值,那么最終的求值結果也是左值。
- 逗號運算符經常被用在for循環當中:
?
4.11類型轉換
- 在C++語言中,某些類型之間有關聯。如果兩種類型有關聯,那么當程序需要其中一種類型的運算對象時,可以用另一種關聯類型的對象或值來替代。換句話說,如果兩種類
- 型可以相互轉換(conversion),那么它們就是關聯的。舉個例子,考慮下面這條表達式,它的目的是將ival初始化為6:
- int ival = 3.541 + 3; / / 編譯器可能會警告該運算損失了精度
- 加法的兩個運算對象類型不同:3.541的類型是double,3的類型是int。C++語言不會直接將兩個不同類型的值相加,而是先根據類型轉換規則設法將運算對象的類型統一后再求值。上述的類型轉換是自動執行的,無須程序員的介入,有時甚至不需要程序員了解。因此,它們被稱作隱式轉換(implicitconversion)。算術類型之間的隱式轉換被設計得盡可能避免損失精度。很多時候,如果表達式中既有整數類型的運算對象也有浮點數類型的運算對象,整型會轉換成浮點型。在上面的例子中,3轉換成double類型,然后執行浮點數加法,所得結果的類型是double。接下來就要完成初始化的任務了。在初始化過程中,因為被初始化的對象的類型無法改變,所以初始值被轉換成該對象的類型。仍以這個例子說明,加法運算得到的double類型的結果轉換成int類型的值,這個值被用來初始化ival。由double向int轉換時忽略掉了小數部分,上面的表達式中,數值6被賦給了ival。
何時發生隱式類型轉換
- 在下面這些情況下,編譯器會自動地轉換運算對象的類型:
- 在大多數表達式中,比int類型小的整型值首先提升為較大的整數類型。
- 在條件中,非布爾值轉換成布爾類型。
- 初始化過程中,初始值轉換成變量的類型;在賦值語句中,右側運算對象轉換成左側運算對象的類型。
- 如果算術運算或關系運算的運算對象有多種類型,需要轉換成同一種類型。
- 如第6章將要介紹的,函數調用時也會發生類型轉換。
4.11.1算術轉換
- 算術轉換(arithmeticconversion)的含義是把一種算術類型轉換成另外一種算術類型,這一點在2.1.2節(第32頁)中已有介紹。算術轉換的規則定義了一套類型轉換的層次,其中運算符的運算對象將轉換成最寬的類型。例如,如果一個運算對象的類型是long double,那么不論另外一個運算對象的類型是什么都會轉換成long double。還有一種更普遍的情況,當表達式中既有浮點類型也有整數類型時,整數值將轉換成相應的浮點類型。
整型提升
- 整型提升 ,負責把小整數類型轉換成較大的整數類型。對于bool、char、signedchar、unsignedchar、short和unsignedshort等類型來說,只要它們所有可能的值都能存在int里,它們就會提升成int類型;否則,提升成unsigned int類型。就如我們所熟知的,布爾值false提升成0、true提升成1。較大的char類型(wchar_t、charl6_t、char32_t)提升成int、unsignedint、long、unsigned long、long long和unsigned long long中最小的一種類型,前提是轉換后的類型要能容納原類型所有可能的值。
無符號類型的運算對象
- 如果某個運算符的運算對象類型不一致,這些運算對象將轉換成同一種類型。但是如果某個運算對象的類型是無符號類型,那么轉換的結果就要依賴于機器中各個整數類型的相對大小了。像往常一樣,首先執行整型提升。如果結果的類型匹配,無須進行進一步的轉換。如果兩個(提升后的)運算對象的類型要么都是帶符號的、要么都是無符號的,則小類型的運算對象轉換成較大的類型。
- 如果一個運算對象是無符號類型、另外一個運算對象是帶符號類型,而且其中的無符號類型不小于帶符號類型,那么帶符號的運算對象轉換成無符號的。例如,假設兩個類型分別是unsigned int和int,則int類型的運算對象轉換成unsigned int類型。需要注意的是,如果int型的值恰好為負值,其結果將以2.1.2節(第32頁)介紹的方法轉換,并帶來該節描述的所有副作用。
- 剩下的一種情況是帶符號類型大于無符號類型,此時轉換的結果依賴于機器。如果無符號類型的所有值都能存在該帶符號類型中,則無符號類型的運算對象轉換成帶符號類型。如果不能,那么帶符號類型的運算對象轉換成無符號類型。例如,如果兩個運算對象的類型分別是long和unsigned int,并且int和long的大小相同,則long類型的運算對象轉換成unsignedint類型;如果long類型占用的空間比int更多,則unsigned int類型的運算對象轉換成long類型。
理解算術轉換
- 要想理解算術轉換,辦法之一就是研究大量的例子:
- 在第一個加法運算中,小寫字母,a,是char型的字符常量,它其實能表示一個數字值(參見2.1.1節,第30頁)。到底這個數字值是多少完全依賴于機器上的字符集,在我們的環境中,a,對應的數字值是97。當把,a,和一個longdouble類型的數相加時,char類型的值首先提升成int類型,然后int類型的值再轉換成longdouble類型。最終我們把這個轉換后的值與那個字面值相加。最后的兩個含有無符號類型值的表達式也比較有趣,它們的結果依賴于機器
4.11.2其他隱式類型轉換
- 除了算術轉換之外還有幾種隱式類型轉換,包括如下幾種。
- 數組轉換成指針:在大多數用到數組的表達式中,數組自動轉換成指向數組首元素的指針:
- 當數組被用作decltype關鍵字的參數,或者作為取地址符(&)、sizeof及typeid(第19.2.2節,732頁將介紹)等運算符的運算對象時,上述轉換不會發生。同樣的,如果用一個引用來初始化數組(參見3.5.1節,第102頁),上述轉換也不會發生。我們將在6.7節(第221頁)看到,當在表達式中使用函數類型時會發生類似的指針轉換。
- 指針的轉換:C++還規定了幾種其他的指針轉換方式,包括常量整數值0或者字面值nullptr能轉換成任意指針類型;指向任意非常量的指針能轉換成void*;指向任意對象的指針能轉換成const void*.15.2.2節(第530頁)將要介紹,在有繼承關系的型間還有另外一種指針轉換的方式。
4.11.3顯式轉換
- 有時我們希望顯式地將對象強制轉換成另外一種類型。例如,如果想在下面的代碼中執行浮點數除法:
- int i,j;? ?double? ?slope? = i/j;
- 就要使用某種方法將i和/或j顯式地轉換成double,這種方法稱作強制類型轉換(cast)
- 然有時不得不使用強制類型轉換,但這種方法本質上是非常危險的
命名的強制類型轉換
- 一個命名的強制類型轉換具有如下形式:
- cast-name<type> (expression); 其中,type是轉換的目標類型而expression是要轉換的值。如果是引用類型 ,則結果是左值 。?cast-name是static cast、dynamic cast、const cast和
reinterpret_cast中的一種。dynamic_cast支持運行時類型識別,我們將在19.2節(第730頁)其做更詳細的介紹。cast-name指定了執行的是哪種轉換。
reinterpret__cast
- reinterpret_cast通常為運算對象的位模式提供較低層次上的重新解釋。舉個例子,假設有如下的轉換
- reinterpret_cast本質上依賴于機器。要想安全地使用reinterpret_cast必須對涉及的類型和編譯器實現轉換的過程都非常了解;
?4 . 1 2 運算符優先級表
?