4.1 c++左值和右值、类型转换
左值和右值
- c++的表達式不是右值就是左值。
- 一個左值表達式的求值結果是一個對象或一個函數,然而以常量對象為代表的某些左值實際上不能作為賦值語句的左側運算對象。此外,雖然某些表達式的求值結果是對象,但它們是右值而非左值。
- 當一個對象被用作右值時,用的是對象的值(內容),當對象被用作左值的時候,用的是對象的身份(在內存中的位置)
- 一個重要的原則是在需要右值的地方可以用左值來代替,但是不能把右值當作左值(也就是位置)使用。當一個左值被當作右值使用時,實際使用的是它的內容(值)。
- 賦值運算符需要一個左值作為其左側運算對象,得到的結果也仍然是一個左值。
- 取地址符作用于一個左值運算對象,返回一個指向該運算對象的指針,這個指針是一個右值。
- 內置解引用運算符、下標運算符、迭代器解引用運算符、string和vector的下標運算符的求值結果都是左值。
- 內置類型和迭代器的遞增遞減運算符作用于左值運算對象,其前置版本所得的結果也是左值。
- 使用關鍵字decltype時,左值和右值也各有不同。如果表達式的求值結果是左值,decltype作用于該表達式(不是變量)得到一個引用類型。
- 假定p的類型是int*,因為解引用運算符生成左值,所以decltype(*p)的結果是int&。
- 另一方面,因為取地址運算符生成右值,所以decltype(&p)的結果是int**,也就是說,結果是一個指向整型指針的指針。
類型轉換
- 在c++語言中,某些類型之間有關聯。如果兩種類型有關聯,那么當程序需要其中一種類型的運算對象時,可以用另一種關聯類型的對象或值來替代。如果兩種類型可以相互轉換,那么它們就是關聯的。
- 加法的兩個運算對象不同,c++語言不會直接將兩個不同類型的值相加,而是實現根據類型轉換規則設法將運算對象的類型統一后再求值。上述的類型轉換是自動執行的,因此它們被稱作隱式類型轉換。
- 算術類型之間的隱式類型轉換被設計的盡可能的避免損失精度。很多時候,如果表達式中既有整數類型的運算對象,又有浮點數類型的運算對象,整數會轉換成浮點型。在上例中,3轉換成double類型,然后執行浮點數加法,所得的結果類型是double
- 接下來完成初始化,在初始化過程中,因為被初始化的對象的類型無法改變,所以初始值被轉換成該對象的類型。在上例中,加法得到的double類型的結果轉換成int類型,這個值被用來初始化ival。由double向int轉換時忽略掉了小數部分,在上述表達式中,數值6被賦給了ival。
何時發生隱式類型轉換
- 在大多數情況下,比int類型小的整型值首先提升為較大的整數類型。
- 在條件中,非布爾值轉換成布爾類型。
- 初始化過程中,初始值轉換成變量的類型;在賦值語句中,右側運算對象轉換成左側運算對象的類型
- 如果算術運算或關系運算的運算對象有多種類型,需要轉換成同一種類型。
- 函數調用時也會發生類型轉換。
算術轉換
- 算術轉換的含義是把一種算術類型轉換成另一種算術類型。
- 算術轉換的規則定義了一套類型轉換的層次,其中運算符的運算對象將轉換成最寬的類型。例如,如果一個運算對象的類型是long double,那么不論另外一個運算對象的類型是什么都會轉換成long double。
- 當表達式中既有浮點類型又有整數類型時,整數類型將轉換成相應的浮點類型。
整型提升
- 整型提升負則把小整數類型換成較大的整數類型。對于bool、char、signed char、unsigned char、short和unsigned short等類型來說,只要它們所有可能的值都存在int里,它們就會提升成int類型;否則,提升成unsigned int類型。
- 較大的char類型提升成int、unsigned int、long、unsigned long、long long和unsigned long long中最小的一種類型,前提是轉換后的類型要能容納原類型所有可能的值。
無符號類型的運算對象
- 如果某個運算符的運算對象類型不一致,這些運算對象將轉換成同一種類型。但是如果某個運算對象的類型是無符號類型,那么轉換的結果就要依賴于機器中各個整數類型的相對大小了。
- 首先執行整型提升。如果結果的類型匹配,無需進行進一步的轉換。如果兩個運算對象的類型要么都是帶符號的、要么都是無符號的,則小類型運算對象轉換為較大的類型。
- 如果一個運算對象是無符號類型、另外一個運算對象是帶符號類型,而且其中的無符號類型不小于帶符號類型,那么帶符號的運算對象轉換成無符號的。例如,假設兩個類型分別是unsigned int 和int,則int類型的運算對象轉換成unsigned int類型。需要注意的是,如果int類型為負值,它的轉換將出現問題。
- 如果帶符號類型大于無符號類型,此時的轉換結果依賴于機器。如果無符號類型的所有值都能存在該帶符號類型中,則無符號類型的運算對象轉換成帶符號類型。如果不能,那么帶符號類型的運算對象轉換成無符號類型。例如,如果兩個運算對象的類型分別為long和unsigned int,并且int和long的大小相同,則long類型的運算對象轉換成unsigned int類型;如果long類型占用空間比int更多,則unsigned int類型的運算對象轉換成long。
其它隱式類型轉換
- 數組轉換成指針:在大多數用到數組的表達式中,數組自動轉換成指向數組首元素的指針
-
當數組被用作decltype關鍵字的參數,或者作為取地址符&、sizeof及typeid等運算符的運算對象時,上述轉換不會發生。
-
如果用一個引用來初始化數組,上述轉換也不會發生。
-
指針的轉換:c++還規定了幾種其他的指針轉換方式,包括常量整數值0或者字面值nullptr能轉換成任意指針類型;指向任意非常量的指針能轉換成void*;指向任意對象的指針能轉換成const void*.
-
在有繼承關系的類型間還有另外一種指針轉換的方式。
-
轉換成布爾類型:存在一種從算術類型或指針類型向布爾類型自動轉換的機制。如果指針或算術類型的值為0,轉換結果為false;否則轉換結果為true
-
轉換成常量:允許將指向非常量類型的指針轉換成指向相應的常量類型的指針,對于引用也是這樣。也就是說,如果T是一種類型,就能將指向T的指針或引用分別轉換成指向const T的指針或引用。
-
相反的轉換并不存在,因為它試圖刪除掉底層const
-
類類型定義的轉換:類類型能定義由編譯器自動執行的轉換,不過編譯器每次只能執行一種類類型的轉換。如果同時提出多個轉換請求,這些請求將被拒絕。
顯式轉換
- 有時希望顯式地將對象強制轉換成另外一種類型,這種方法稱作強制類型轉換。
命名的強制類型轉換
- 一個命名的強制類型轉換具有如下形式
- 其中,type是轉換的目標類型,而expression是要轉換的值。如果type是引用類型,則結果是左值。
- cast-name是static_cast、dynamic_cast、const_cast和reinterpret_cast中的一種。dynamic_cast支持運行時類型識別,cast_name指定了執行的是哪種轉換。
ststic_cast
- 任何具有明確定義的類型轉換,只要不包含底層const,都可以使用static_cast.
- 當需要把一個較大的算術類型賦值給較小的類型時,static_cast非常有用。此時,強制類型轉換告訴編譯器,我們知道且不在乎潛在的精度損失。一般來說,如果編譯器發現了一個較大的算術類型試圖賦值給較小的類型,就會給出警告信息;但是當執行了顯式類型轉換后,警告信息就會被關閉了。
- static_cast對于編譯器無法自動執行的類型轉換非常有用。例如,可以使用static_cast找回存在于void*指針中的值。
- 把指針存放在void*中,且使用static_cast將其強制轉換回原來的類型時,應該確保指針的值保持不變。也就是說,強制類型轉換的原地址相等,因此必需確保轉換后所得的類型就是指針所指的類型。類型一旦不符,將產生未定義的后果。
const_cast
- const_cast只能改變運算對象的底層const
- 對于將常量對象轉換成非常量對象的行為,一般稱其為"去掉const性質"。一旦去掉了某個對象的const性質,編譯器就不再阻止我們對該對象進行寫操作了。如果對象本身不是一個常量,使用強制類型轉換獲得寫權限是合法的行為。然而如果對象是一個常量,再使用const_cast執行寫操作就會產生未定義的后果。
- 只有const_cast能改變表達式的常量屬性,使用其它形式的命名強制類型轉換改變表達式的常量屬性都將引發編譯器錯誤。同樣的,也不能用const_cast改變表達式的類型
- const_cast常常用于有函數重載的上下文中。
reinterpreter_cast
- reinterpret_cast通常為運算對象的位模式提供較低層次上的重新解釋。假設有如下轉換:
- pc所指的真實對象是一個int而非字符,如果把pc當成普通的字符指針使用就可能再運行時發生錯誤,例如:
- 可能導致異常的運行時行為。
- 使用reinterpret_cast是非常危險的,用pc初始化str的例子很好的證明了這一點。其中關鍵問題是類型改變了,但編譯器沒有給出任何警告或錯誤的提示信息。
- 當用一個int的地址初始化pc時,由于顯式的聲稱這種轉換合法,所以編譯器沒法知道它實際存放的是指向int的指針。最終的結果是,在上例中雖然用pc初始化str沒什么實際意義,甚至還可能引發更糟糕的后果,但僅從語法上而言,這種操作沒錯。
- 查找這類問題的原因非常困難,如果將ip強制轉換成pc的語句和用pc初始化string對象的語句分屬不同文件就更是如此。
- reinterpret_cast本質上依賴于機器。要想安全的使用reinterpret_cast必需對涉及的類型和編譯器首先的轉換過程非常了解。
建議:避免強制類型轉換
強制類型轉換干擾了正常的類型檢查,強烈建議程序員避免使用強制類型轉換。在有重載函數的上下文中使用const_cast無可厚非,但在其他情況下使用conat_cast也就意味著程序的某種設計缺陷。其它強制類型轉換,都應該反復斟酌能否以其他方式實現相同的目標。就算實在無法避免,也應該盡量限制類型轉換的作用域,并且記錄對相關類型的所有假定,這樣可以減少錯誤發生的機會。
舊式的強制類型轉換
- 在早期版本的c++語言中,顯式的進行強制類型轉換包含兩種形式
- 根據所涉及的類型不同,舊式的強制類型轉換分別具有與const_cast、static_cast和reinterpret_cast相似的行為。在某處執行舊式的強制類型轉換時,如果換成const_cast和static_cast也合法,則其行為與對應的命名轉換一致。如果替換后不合法,則舊式強制類型轉換執行與reinterpret_cast類似的功能的效果與使用reinterpret_cast一樣
與命名的強制類型轉換相比,舊式的強制類型轉換從表現形式上來說不那么清晰明了,所以一旦轉換過程出現問題,追蹤起來也更加困難。
總結
以上是生活随笔為你收集整理的4.1 c++左值和右值、类型转换的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RT-Thread 模拟器 simula
- 下一篇: s3c2440移植MQTT