本節書摘來自華章社群《c語言程式設計:問題與求解方法》一書中的第3章,第3.8節不同類型資料之間的類型轉換,作者:何 勤,更多章節内容可以通路雲栖社群“華章社群”公衆号檢視
3.8 不同類型資料之間的類型轉換
機器語言的算術運算指令比c語言算術表達式的限制更多。為了讓計算機執行機器指令中的算術運算,通常不僅要求兩個操作數有相同的長度(位元組數),而且還要求資料的存儲方式也相同。比如同是單精度浮點型數。
在c語言中,最好把同類型的常量值賦給同一類型的變量,或者使用同類型的常量和變量進行算術運算或關系運算。
然而在c語言程式中,允許在表達式中混合使用各種不同類型的資料。在一個表達式中,可以同時出現整型、浮點型、字元型的常量和變量。在這種情況下,c語言編譯程式通常需要生成一些附加的指令,在執行一條運算類指令之前,将其中一個操作數的類型轉換成另一個操作數的類型。
下面讨論的類型轉換都是有符号型的變量和常量之間的類型轉換,不涉及無符号的變量(和常量)。這适用于正常的程式設計應用範圍。一些進階語言(比如java語言),就隻有有符号型的常量和變量。
類型轉換分為自動類型轉換和強制類型轉換兩種。自動類型轉換指的是編譯器在将語句(或表達時)翻譯成指令時,由編譯程式自動進行的類型轉換。在以下四種情況中會出現自動類型轉換:
1)二進制算術運算符或關系運算符兩邊的操作數類型不一緻。
2)指派運算符兩邊的操作數類型不一緻。
3)函數調用時實際參數與形式參數要求的類型不一緻。
4)函數調用結束時傳回值的類型與要求的類型不一緻。
其中,第3種和第4種類型轉換讨論,請參見第7章。
本章以下僅讨論前兩種情況下的自動類型轉換。
(1)算術或關系表達式中的類型轉換
在算術(或者關系)表達式中,出現在二進制運算符兩側的運算量可以是不同類型的,這時就涉及類型之間的轉換問題。如果沒有出現強制類型轉換,那麼簡單的自動類型轉換規則分為三種:
1)隻要兩個操作數中出現了char類型或 short 型,首先都将無條件地自動提升為int型。
2)在兩個操作數的類型都不是浮點型的情況下,自動進行了第一種規定的無條件的類型提升之後,按照以下方式對操作數的類型再次進行有條件的提升。這個條件是兩個操作數之中有一個數是long型,即将int提升為long型。
舉例來說,對于“short n1 ; long num;”,要求計算表達式n1 + num。編譯器先将變量 n1自動提升為int型。由于兩個操作數中有一個變量num是long型,是以,剛剛提升為int 型的變量n1的值将再次提升為long型。
延伸與拓展:整型類型提升的技術内幕
增加臨時存放此數的位元組和對這些位元組進行符号位的擴充,這是由于計算機是用補碼來表示有符号整數的。隻有對補碼進行符号位的擴充,補碼表示的值才不會改變。參見本章提高部分有關補碼的讨論。
3)兩個操作數都是浮點數或者其中有一個是浮點數的情況下,提升規則如下:
long double (這是c99标準規定的類型)
double
float
也就是說:
如果在一個表達式(或一個二進制算數或關系運算符)中出現了long double 型的運算量,則此表達式計算出的最終值是long double型的。
如果一個表達式(或一個二進制算數或關系運算符)中出現了double 型的運算量(但沒有出現long double型量),則此表達式計算出的最終值是double型的。
如果一個表達式(或一個二進制算數或關系運算符)中出現了float 型的運算量(但沒有出現double型量或long double型量),則此表達式計算出的最終值是float型的。
如果兩個操作數中還有一個int型或 long 型的整型量,那麼此整型數就會根據另一個浮點操作數的類型進行轉換。
讀者還要注意的是:資料類型的轉換,并不是在計算表達式的值之前一次性完成的,而是根據規定的運算順序,逐漸進行類型轉換的。例如,對于如下表達式:
56.91+'a'*32
是先按照轉換規則,将char型的量'a'轉變為int型值(第一種類型提升)進行乘法運算;得到一個整數值以後,再按照轉換規則轉變成double型(這是由于前一個參與運算的常量59.61被系統預設為double型常量),然後再與56.91進行雙精度浮點數加法運算。
(2)指派語句(或指派表達式)中的類型轉換
在指派語句(或指派表達式)中,如果指派運算符右邊表達式計算結果的類型與左邊變量的類型不一緻,則會自動進行類型轉換。類型轉換的規則很簡單:将表達式計算出的值轉變成指派号左邊變量的類型。
如果指派号右邊的資料類型的取值範圍寬于左邊變量類型的取值範圍,通常會發生精度的額外丢失或者錯誤。
例如,如果定義“char d_char ; int d_int ; float d_float ; double d_double ;”,那麼以下指派語句(或指派表達式)是不會有任何問題的,因為右邊的資料類型“不寬于”左邊變量類型的取值範圍。
d_int = d_char;
d_float = d_int;
d_double = d_float;
但是,如果将以上幾條指派語句(或指派表達式)中的變量調換位置:
d_char= d_int;
d_int =d_float;
d_float= d_double;
通常都會出現問題。
對于第1條語句“d_char= d_int;”,在d_int的值超過255時,在char隻占8位記憶體的計算機上必然出錯(溢出)。即使d_int的取值在128~255之間,也不一定正确。這要看你正在使用的c編譯器中,char類型本質上到底是有符号的整數(取值在–128~127之間)還是無符号的整數(取值在0~255之間)。如果是前者,就會出現把一個正整數變成了一個負整數并且存放到變量d_char中的錯誤。
對于第2條語句“d_int =d_float;”,必然會使d_float的小數部分丢失(不會四舍五入)。更為嚴重的是,如果d_float的值超出了int類型的取值範圍(這或許是很常見的,因為有些c編譯器中,int型的最大值是32767),那麼變量d_int中的值也必然出錯(溢出)。
第3條語句的情況,讀者可自行分析。
(3)強制類型轉換
如果自動類型轉換滿足不了需要,則可以要求編譯程式進行強制類型轉換。強制類型轉換是通過使用強制類型轉換運算符(由圓括号包覆類型名構成)來實作的。其格式為:
(類型名)(被轉換的表達式)
其功能是把表達式的運算結果,強制轉換成類型名所給定的類型。例如,(float) m 把整型變量m的值強制轉換為單精度浮點型。(int)(3.86x–y)把表達式3.86x–y的值強制轉換為整型。
在進行強制類型轉換時應注意:類型名必須用圓括号包覆,不能省略;需進行類型轉換的表達式也要加圓括号(單個變量或常量可以不加括号)。例如,如果錯把(int)(x–y)寫成(int)x–y,則會出現僅僅把x轉換成int類型之後,再與y相加的問題。
以下兩種情況下要使用強制類型轉換:
1)如果擔心兩個float型量x,y的乘積超出float型的表示範圍(即産生溢出),那就要這樣書寫語句:z=(double )xy;。其中,變量z是double類型,y的類型會自動提升為double型。注意,另一個很類似的語句:z=(double )(xy);是錯誤的,因為在進行強制類型轉換前,x*y的乘積值很可能已經超出了float型的表示範圍。
2)如果擔心兩個int型量i,j的乘積超出int型的表示範圍,那就要這樣書寫語句: k=(long int )i*j;,其中k是long int類型的變量。
強制類型轉換如果使用不當,出現的問題與指派語句中自動類型轉換的類似。
注意:無論是強制類型轉換或是自動類型轉換,都隻是為了本次運算的需要而對從變量中取出的值進行的臨時性轉換,不會改變變量定義時對該變量指定的類型,也不會改變變量所對應記憶體單元中存放的值。
無符号的變量(和常量)之間的類型轉換,與本節讨論的情況完全類似。強烈建議讀者在通常應用中不要在表達式中使用無符号的常量與變量。無符号的常量和變量最好局限于應用在系統程式設計和嵌入式程式設計方面。是以,讀者應盡量避免在同一個表達式中,同時使用有符号和無符号的量。無符号與有符号量之間的類型轉換的讨論,請讀者參考相關的教科書。