C++之Primer類型轉(zhuǎn)換方式
類型轉(zhuǎn)換
在C++語言中,某些類型之間有關(guān)聯(lián)。如果兩種類型有關(guān)聯(lián),那么當程序需要其中一種類型的運算對象時,可以用另一種關(guān)聯(lián)類型的對象或值來替代。
換句話說,如果兩種類型可以相互轉(zhuǎn)換(conversion),那么它們就是關(guān)聯(lián)的。
舉個例子,考慮下面這條表達式,它的目的是將ival初始化為6:
int ival = 3.541 + 3;//編譯器可能會警告該運算損失了精度
加法的兩個運算對象類型不同:3.541的類型是double,3的類型是int。C++語言不會直接將兩個不同類型的值相加,而是先根據(jù)類型轉(zhuǎn)換規(guī)則設(shè)法將運算對象的類型統(tǒng)一后再求值。上述的類型轉(zhuǎn)換是自動執(zhí)行的,無須程序員的介入,有時甚至不需要程序員了解。因此,它們被稱作隱式轉(zhuǎn)換(implicit conversion)。
算術(shù)類型之間的隱式轉(zhuǎn)換被設(shè)計得盡可能避免損失精度。很多時候,如果表達式中既有整數(shù)類型的運算對象也有浮點數(shù)類型的運算對象,整型會轉(zhuǎn)換成浮點型。在上面的例子中,3轉(zhuǎn)換成double類型,然后執(zhí)行浮點數(shù)加法,所得結(jié)果的類型是double。
接下來就要完成初始化的任務(wù)了。在初始化過程中,因為被初始化的對象的類型無法改變,所以初始值被轉(zhuǎn)換成該對象的類型。仍以這個例子說明,加法運算得到的double類型的結(jié)果轉(zhuǎn)換成int類型的值,這個值被用來初始化ival。由double向int轉(zhuǎn)換時忽略掉了小數(shù)部分,上面的表達式中,數(shù)值6被賦給了ival。
何時發(fā)生隱式類型轉(zhuǎn)換
在下面這些情況下,編譯器會自動地轉(zhuǎn)換運算對象的類型:
在大多數(shù)表達式中,比int類型小的整型值首先提升為較大的整數(shù)類型。在條件中,非布爾值轉(zhuǎn)換成布爾類型。初始化過程中,初始值轉(zhuǎn)換成變量的類型:在賦值語句中,右側(cè)運算對象轉(zhuǎn)換成左側(cè)運算對象的類型。如果算術(shù)運算或關(guān)系運算的運算對象有多種類型,需要轉(zhuǎn)換成同一種類型。函數(shù)調(diào)用時也會發(fā)生類型轉(zhuǎn)換。
算術(shù)轉(zhuǎn)換
算術(shù)轉(zhuǎn)換(arithmetic conversion)的含義是把一種算術(shù)類型轉(zhuǎn)換成另外一種算術(shù)類型。算術(shù)轉(zhuǎn)換的規(guī)則定義了一套類型轉(zhuǎn)換的層次,其中運算符的運算對象將轉(zhuǎn)換成最寬的類型。例如,如果一個運算對象的類型是long double,那么不論另外一個運算對象的類型是什么都會轉(zhuǎn)換成long double。還有一種更普遍的情況,當表達式中既有浮點類型也有整數(shù)類型時,整數(shù)值將轉(zhuǎn)換成相應(yīng)的浮點類型。
整型提升
整型提升(integral promotion)負責把小整數(shù)類型轉(zhuǎn)換成較大的整數(shù)類型。對于bool、char、signed char、unsigned char、short和unsigned short等類型來說,只要它們所有可能的值都能存在int里,它們就會提升成int類型,否則,提升成unsigned int類型。就如我們所熟知的,布爾值false提升成0、true提升成1。
較大的char類型(wchar_t、char16_t、vchar32_t)提升成int、unsigned int、long、unsigned long、long long和unsigned long long中最小的一種類型,前提是轉(zhuǎn)換后的類型要能容納原類型所有可能的值。
無符號類型的運算對象
如果樹個運算符的運算對象類型不一致,這些運算對象將轉(zhuǎn)換成同一種類型。但是如果樹個運算對象的類型是無符號類型,那么轉(zhuǎn)換的結(jié)果就要依賴于機器中各個整數(shù)類型的相對大小了。
像往常一樣,首先執(zhí)行整型提升。如果結(jié)果的類型匹配,無須進行進一步的轉(zhuǎn)換。如果兩個(提升后的運算對象的類型要么都是帶符號的、要么都是無符號的,則小類型的運算對象轉(zhuǎn)換成較大的類型。
如果一個運算對象是無符號類型、另外一個運算對象是帶符號類型,而一其中的無符號類型不小于帶符號類型,那么帶符號的運算對象轉(zhuǎn)換成無符號的。例如,假設(shè)兩個類型分別是unsigned int和int,則int類型的運算對象轉(zhuǎn)換成unsigned int類型。需要注意的是,如果int型的值恰好為負值。
剩下的一種情況是帶符號類型大于無符號類型,此時轉(zhuǎn)換的結(jié)果依賴于機器。如果無符號類型的所有值都能存在該帶符號類型中,則無符號類型的運算對象轉(zhuǎn)換成帶符號類型。如果不能,那么帶符號類型的運算對象轉(zhuǎn)換成無符號類型。例如,如果兩個運算對象的類型分別是long和unsigned int,并且int和long的大小相同,則long類型的運算對象轉(zhuǎn)換成unsigned int類型;如果long類型占用的空間比int更多,則unsigned int類型的運算對象轉(zhuǎn)換成long類型。
理解算術(shù)轉(zhuǎn)換
要想理解算術(shù)轉(zhuǎn)換,辦法之一就是研究大量的例子:
bool flag;char val; short sval; unsigned short usval; int ival; unsigned int uival; long lval;unsigned long ulval; float fval; double dval; 3.14159L+ 'a'; //“a“提升成int,然后該int值轉(zhuǎn)換成long double dval+ival;//ival轉(zhuǎn)換成double dval+fval;//fval轉(zhuǎn)換成double ival=dval;//dval轉(zhuǎn)握成(切除小數(shù)部分后)int flag=dval;//如果dval是0,則flag是false,否則f1ag是Lrue cval+fval;//cval提升成int,然后該int值轉(zhuǎn)換成float sval+cval;//sval和cval都提升成int cval+lval;//cval轉(zhuǎn)援成long ival+ulval;//ival轉(zhuǎn)換成unsigned long usval+ival;//根據(jù)unsigned short和int所占空間的大小進行提升 uival+lval;//根據(jù)hnsigned int和long所占空間的大小進行轉(zhuǎn)換
在第一個加法運算中,小寫字母’a’是char型的字符常量,它其實能表示一個數(shù)字值。到底這個數(shù)字值是多少完全依賴于機器上的字符集,在我們的環(huán)境中,‘a’對應(yīng)的數(shù)字值是97。當把’a’ 和一個long double類型的數(shù)相加時,char類型的值首先提升成int類型,然后int類型的值再轉(zhuǎn)換成long double類型。最終我們把這個轉(zhuǎn)換后的值與那個字面值相加。最后的兩個含有無符號類型值的表達式也比較有趣,它們的結(jié)果依賴于機器。
其他隱式類型轉(zhuǎn)換運
除了算術(shù)轉(zhuǎn)換之外還有幾種隱式類型轉(zhuǎn)換,包括如下幾種。
數(shù)組轉(zhuǎn)換成指針:在大多數(shù)用到數(shù)組的表達式中,數(shù)組自動轉(zhuǎn)換成指向數(shù)組首元素的指針:
int ia[10];//含有10個整數(shù)的數(shù)組 int*ip = a;//ia轉(zhuǎn)換成指向數(shù)組首元素的指針
當數(shù)組被用作 decltype 關(guān)鍵字的參數(shù),或者作為取地址符(&)、sizeof及typeid等運算符的運算對象時,上述轉(zhuǎn)換不會發(fā)生。同樣的,如果用一個引用來初始化數(shù)組,上述轉(zhuǎn)換也不會發(fā)生。指針的轉(zhuǎn)換:C++還規(guī)定了幾種其他的指針轉(zhuǎn)換方式,包括常量整數(shù)值 0 或者字面值 nullptr 能轉(zhuǎn)換成任意指針類型;指向任意非常量的指針能轉(zhuǎn)換成void*;指向任意對象的指針能轉(zhuǎn)換成const void*。
型間還有另外一種指針轉(zhuǎn)換的方式。
轉(zhuǎn)換成布爾類型:存在一種從算術(shù)類型或指針類型向布爾類型自動轉(zhuǎn)換的機制。如果指針或算術(shù)類型的值為0,轉(zhuǎn)換結(jié)果是false;否則轉(zhuǎn)換結(jié)果是true:
char*cp=get_string(); if(cp) /*...*/ //如果指針cp不是0,條件為真 while(*cp)/*...*///如果*cp不是空字符,條件為真
轉(zhuǎn)換成常量:允許將指向非常量類型的指針轉(zhuǎn)換成指向相應(yīng)的常量類型的指針,對于引用也是這樣。也就是說,如果0是一種類型,我們就能將指向0的指針或引用分別轉(zhuǎn)換成指向const的指針或引用:
int i; const int &j =i; //非常量轉(zhuǎn)換成const int的引用 const int*p=&i; //非常量的地址轉(zhuǎn)換成const的地址 int &r = j,*q= p; //錯誤:不允許const轉(zhuǎn)換成非常量
相反的轉(zhuǎn)換并不存在,因為它試圖刪除掉底層const。類類型定義的轉(zhuǎn)換:類類型能定義由編譯器自動執(zhí)行的轉(zhuǎn)換,不過編譯器每次只能執(zhí)行一種類類型的轉(zhuǎn)換。我們將看到一個例子,如果同時提出多個轉(zhuǎn)換請求,這些請求將被拒絕。
我們之前的程序已經(jīng)使用過類類型轉(zhuǎn)換:一處是在需要標準庫string類型的地方使用C風格守符串;另一處是在條件部分讀入istream:
string s, t= "a value";//字符串字面值轉(zhuǎn)換成string類型 while(cin >> s) // while 的條件部分把cin 轉(zhuǎn)換成布爾值
條件( cin >> s )讀入cin的內(nèi)容并將cin作為其求值結(jié)果。條件部分本來需要一個布爾類型的值,但是這里實際檢查的是istream類型的值。幸好,IO庫定義了從istream向布爾值轉(zhuǎn)換的規(guī)則,根據(jù)這一規(guī)則,cin自動地轉(zhuǎn)換成布爾值。所得的布爾值到底是什么由輸入流的狀態(tài)決定,如果最后一次讀入成功,轉(zhuǎn)換得到的布爾值是true:相反,如果最后一次讀入不成功,轉(zhuǎn)換得到的布爾值是false。
顯式轉(zhuǎn)換
有時我們希望顯式地將對象強制轉(zhuǎn)換成另外一種類型。例如,如果想在下面的代碼中執(zhí)行浮點數(shù)除法:
int i,j; double slope=i;
就要使用某種方法將i和/或j顯式地轉(zhuǎn)換成double,這種方法稱作強制類型轉(zhuǎn)換(cast)。
WARNING: 雖然有時不得不使用強制類型轉(zhuǎn)換,但這種方法本質(zhì)上是非常危險的。
命名的強制類型轉(zhuǎn)換
一個命名的強制類型轉(zhuǎn)換具有如下形式:
cast-name<type>(expression)
其中,type是轉(zhuǎn)換的目標類型而 expression 是要轉(zhuǎn)換的值。如果type是引用類型,則結(jié)果是左值。cast-name是 static_cast、dynamic_cast、const_cast 和reinterpret_cast中的一種。dynamic_cast支持運行時類型識別。
static_cast
任何具有明確定義的類型轉(zhuǎn)換,只要不包含底層const,都可以使用static_cast。例如,通過將一個運算對象強制轉(zhuǎn)換成double類型就能使表達式執(zhí)行浮點數(shù)除法:
//進行強制類型轉(zhuǎn)換以便執(zhí)行浮點數(shù)除法 double slope=static_cast<double>(j)/i;
當需要把一個較大的算術(shù)類型賦值給較小的類型時,static_cast非常有用。此時,強制類型轉(zhuǎn)換告訴程序的讀者和編譯器:我們知道并且不在乎潛在的精度損失。一般來說,如果編譯器發(fā)現(xiàn)一個較大的算術(shù)類型試圖賦值給較小的類型,就會給出警告信息;但是當我們執(zhí)行了顯式的類型轉(zhuǎn)換后,警告信息就會被關(guān)閉了。
static_cast對于編詳器無法自動執(zhí)行的類型轉(zhuǎn)換也非常有用。例如,我們可以使用static_cast找回存在于void*指針中的值:
void+p=&d;//正確:任何非常量對象的地址都能存入void*
//正確:將void*轉(zhuǎn)接回初始的指針類型
double*dp=static_cast<double*>(p);當我們把指針存放在void*中,并且使用static_cast將其強制轉(zhuǎn)換回原來的類型時,應(yīng)該確保指針的值保持不變。也就是說,強制轉(zhuǎn)換的結(jié)果將與原始的地址值相等,因此我們必須確保轉(zhuǎn)換后所得的類型就是指針所指的類型。類型一旦不符,將產(chǎn)生未定義的后果。
const_cast
const_cast只能改變運算對象的底層const:
const char*pc; char*p=const_cast<char*>(pc);//正確:但是通過p寫值是未定義的行為
對于將常量對象轉(zhuǎn)換成非常量對象的行為,我們一般稱其為“去掉const性質(zhì)(cast away the const)“。一旦我們?nèi)サ袅藰鋫€對象的const性質(zhì),編譯器就不再阻止我們對該對象進行寫操作了。如果對象本身不是一個常量,使用強制類型轉(zhuǎn)換獲得寫權(quán)限是合法的行為。然而如果對象是一個常量,再使用const_cast執(zhí)行寫操作就會產(chǎn)生未定義的后果。
只有const_cast能改變表達式的常量屬性,使用其他形式的命名強制類型轉(zhuǎn)換改變表達式的常量屬性都將引發(fā)編譯器錯誤。同樣的,也不能用const_cast改變表達式的類型:
const char*cp; //錯誤:static_cast不能轉(zhuǎn)換據(jù)const性質(zhì) char*q = static_cast<char*>(cp); static_cast<string>(cp);//正確:字符串字面值轉(zhuǎn)換成string類型 const_cast<string>(cp);//錯誤:const_cast只改變常量屬性
const_cast常常用于有函數(shù)重載的上下文中
reinterpret_cast
reinterpret_cast通常為運算對象的位模式提供較低層次上的重新解釋。舉個例子,假設(shè)有如下的轉(zhuǎn)換
int *ip; char*pc=reinterpret_cast<char*>(ip);
我們必須牢記pc所指的真實對象是一個int而非字符,如果把pc當成普通的字符指針使用就可能在運行時發(fā)生錯誤。例如:
string str(pc);
可能導致異常的運行時行為。
使用reinterpret_cast是非常危險的,用pc初始化str的例子很好地證明了這一點。其中的關(guān)鍵問題是類型改變了,但編譯器沒有給出任何警告或者錯誤的提示信息。當我們用一個int的地址初始化pc時,由于顯式地聲稱這種轉(zhuǎn)換合法,所以編譯器不會發(fā)出任何警告或錯誤信息。接下來再使用pc時就會認定它的值是char*類型,編譯器沒法知道它實際存放的是指向int的指針。最終的結(jié)果就是,在上面的例子中雖然用pc初始化str沒什么實際意義,甚至還可能引發(fā)更糟糕的后果,但僅從語法上而言這種操作無可指摘。查找這類問題的原因非常困難,如果將ip強制轉(zhuǎn)換成pc的語句和用pc初始化string對象的語句分屬不同文件就更是如此。
WRNING: reinterpret_cast本質(zhì)上依賴于機器.要想安全地使用reinterpret_cast必須對涉及的類型和編譯囂實現(xiàn)轉(zhuǎn)換的過程都非常了解。
建議:避免強制類型轉(zhuǎn)換
強制類型轉(zhuǎn)換干擾了正常的類型檢查,因此我們強烈建議程序員避免使用強制類型轉(zhuǎn)換。這個建議對于reinterpret_cast尤其適用,因為此類類型轉(zhuǎn)換總是充滿了風險。在有重載函數(shù)的上下文中使用const_cast無可厚非,關(guān)于這一點將在;但是在其他情況下使用const_cast也就意味著程序存在某種設(shè)計缺陷。其他強制類型轉(zhuǎn)換,比如static_cast和dynamic_cast,都不應(yīng)該頻素使用。每次書寫了一條強制類型轉(zhuǎn)換語句,都應(yīng)該反復斟酌能否以其他方式實現(xiàn)相同的目標。就算實在無法避免,也應(yīng)該盡量限制類型轉(zhuǎn)換值的作用域,并且記錄對相關(guān)類型的所有假定,這樣可以減少錯誤發(fā)生的機會。′
舊式的強制類型轉(zhuǎn)換
在早期版本的C++語言中,顯式地進行強制類型轉(zhuǎn)換包含兩種形式:
type(expr);//函數(shù)形式的強制類型轉(zhuǎn)換 (type)expr;//C語言風格的強制類型轉(zhuǎn)換
根據(jù)所涉及的類型不同,舊式的強制類型轉(zhuǎn)換分別具有與const_cast、static_cast或reinterpret_cast相似的行為。當我們在某處執(zhí)行舊式的強制類型轉(zhuǎn)換時,如果換成const_cast和static_cast也合法,則其行為與對應(yīng)的命名轉(zhuǎn)換一致。如果替換后不合法,則舊式強制類型轉(zhuǎn)換執(zhí)行與reinterpret_cast類似的功能:
char *pc=(char*)ip;//ip是指向整數(shù)的指針
的效果與使用retnterpret_cast一樣。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
C++ 中的INT_MAX,INT_MIN數(shù)值大小操作
這篇文章主要介紹了C++ 中的INT_MAX,INT_MIN數(shù)值大小操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03
VSCODE+cmake配置C++開發(fā)環(huán)境的實現(xiàn)步驟
這篇文章主要介紹了VSCODE+cmake配置C++開發(fā)環(huán)境的實現(xiàn)步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-03-03
C++ 將字符串值賦給CHAR數(shù)組的實現(xiàn)
這篇文章主要介紹了C++ 將字符串值賦給CHAR數(shù)組的實現(xiàn),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01
c語言實現(xiàn)從源文件從文本到可執(zhí)行文件經(jīng)歷的過程
這篇文章主要介紹了c語言實現(xiàn)從源文件從文本到可執(zhí)行文件經(jīng)歷的過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07

