c++四種轉換
C++ 型別轉換(C風格的強制轉換):
在C++基本的資料型別中,可以分為四類:整型,浮點型,字元型,布林型。其中數值型包括 整型與浮點型;字元型即為char。
(1)將浮點型資料賦值給整型變數時,捨棄其小數部分。
(2)將整型資料賦值給浮點型變數時,數值不變,但是以指數形式儲存。
(3)將double型資料賦值給float型變數時,注意數值範圍溢位。
(4)字元型資料可以賦值給整型變數,此時存入的是字元的ASCII碼。
(5)將一個int,short或long型資料賦值給一個char型變數,只將低8位原封不動的送到char型變數中。
(6)將有符號型資料賦值給長度相同的無符號型變數,連同原來的符號位一起傳送。
C++強制型別轉換:
在C++語言中新增了四個關鍵字static_cast、const_cast、reinterpret_cast和dynamic_cast。這四個關鍵字都是用於強制型別轉換的。
新型別的強制轉換可以提供更好的控制強制轉換過程,允許控制各種不同種類的強制轉換。
C++中風格是static_cast<type>(content)。C++風格的強制轉換其他的好處是,它們能更清晰的表明它們要幹什麼。程式設計師只要掃一眼這樣的程式碼,就能立即知道一個強制轉換的目的。
1) static_cast
在C++語言中static_cast用於資料型別的強制轉換,強制將一種資料型別轉換為另一種資料型別。例如將整型資料轉換為浮點型資料。
[例1]C語言所採用的型別轉換方式:
int a = 10; int b = 3; double result = (double)a / (double)b;
例1中將整型變數a和b轉換為雙精度浮點型,然後相除。在C++語言中,我們可以採用static_cast關鍵字來進行強制型別轉換,如下所示。
[例2]static_cast關鍵字的使用:
int a = 10; int b = 3; double result = static_cast<double>(a) / static_cast<double>(b);
在本例中同樣是將整型變數a轉換為雙精度浮點型。採用static_cast進行強制資料型別轉換時,將想要轉換成的資料型別放到尖括號中,將待轉換的變數或表示式放在元括號中,其格式可以概括為如下形式:
用法:static_cast <型別說明符> (變數或表示式)
它主要有如下幾種用法:
(1)用於類層次結構中基類和派生類之間指標或引用的轉換
進行上行轉換(把派生類的指標或引用轉換成基類表示)是安全的
進行下行轉換(把基類的指標或引用轉換為派生類表示),由於沒有動態型別檢查,所以是不安全的
(2)用於基本資料型別之間的轉換,如把int轉換成char。這種轉換的安全也要開發人員來保證
(3)把空指標轉換成目標型別的空指標
(4)把任何型別的表示式轉換為void型別
注意:static_cast不能轉換掉expression的const、volitale或者__unaligned屬性。
static_cast:可以實現C++中內建基本資料型別之間的相互轉換。
如果涉及到類的話,static_cast只能在有相互聯絡的型別中進行相互轉換,不一定包含虛擬函式。
2) const_cast
在C語言中,const限定符通常被用來限定變數,用於表示該變數的值不能被修改。
而const_cast則正是用於強制去掉這種不能被修改的常數特性,但需要特別注意的是const_cast不是用於去除變數的常量性,而是去除指向常數物件的指標或引用的常量性,其去除常量性的物件必須為指標或引用。
用法:const_cast<type_id> (expression)
該運算子用來修改型別的const或volatile屬性。除了const 或volatile修飾之外, type_id和expression的型別是一樣的。
常量指標被轉化成非常量指標,並且仍然指向原來的物件;
常量引用被轉換成非常量引用,並且仍然指向原來的物件;常量物件被轉換成非常量物件。
[例3]一個錯誤的例子:
const int a = 10; const int * p = &a; *p = 20; //compile error int b = const_cast<int>(a); //compile error
在本例中出現了兩個編譯錯誤,第一個編譯錯誤是*p因為具有常量性,其值是不能被修改的;另一處錯誤是const_cast強制轉換物件必須為指標或引用,而例3中為一個變數,這是不允許的!
[例4]const_cast關鍵字的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
在本例中,我們將變數a宣告為常量變數,同時聲明瞭一個const指標指向該變數(此時如果宣告一個普通指標指向該常量變數的話是不允許的,Visual Studio 2010編譯器會報錯)。
之後我們定義了一個普通的指標*q。將p指標通過const_cast去掉其常量性,並賦給q指標。之後我再修改q指標所指地址的值時,這是不會有問題的。
最後將結果打印出來,執行結果如下:
10 20 20
002CFAF4 002CFAF4 002CFAF4
檢視執行結果,問題來了,指標p和指標q都是指向a變數的,指向地址相同,而且經過除錯發現002CFAF4地址內的值確實由10被修改成了20,這是怎麼一回事呢?為什麼a的值打印出來還是10呢?
其實這是一件好事,我們要慶幸a變數最終的值沒有變成20!變數a一開始就被宣告為一個常量變數,不管後面的程式怎麼處理,它就是一個常量,就是不會變化的。試想一下如果這個變數a最終變成了20會有什麼後果呢?對於這些簡短的程式而言,如果最後a變成了20,我們會一眼看出是q指標修改了,但是一旦一個專案工程非常龐大的時候,在程式某個地方出現了一個q這樣的指標,它可以修改常量a,這是一件很可怕的事情的,可以說是一個程式的漏洞,畢竟將變數a宣告為常量就是不希望修改它,如果後面能修改,這就太恐怖了。
在例4中我們稱“*q=20”語句為未定義行為語句,所謂的未定義行為是指在標準的C++規範中並沒有明確規定這種語句的具體行為,該語句的具體行為由編譯器來自行決定如何處理。對於這種未定義行為的語句我們應該儘量予以避免!
從例4中我們可以看出我們是不想修改變數a的值的,既然如此,定義一個const_cast關鍵字強制去掉指標的常量性到底有什麼用呢?我們接著來看下面的例子。
例5:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
在例5中我們定義了一個函式,用於在a陣列中尋找val值,如果找到了就返回該值的地址,如果沒有找到則返回NULL。函式Search返回值是const指標,當我們在a陣列中找到了val值的時候,我們會返回val的地址,最關鍵的是a陣列在main函式中並不是const,因此即使我們去掉返回值的常量性有可能會造成a陣列被修改,但是這也依然是安全的。
對於引用,我們同樣能使用const_cast來強制去掉常量性,如例6所示。
例6:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
瞭解了const_cast的使用場景後,可以知道使用const_cast通常是一種無奈之舉,同時也建議大家在今後的C++程式設計過程中一定不要利用const_cast去掉指標或引用的常量性並且去修改原始變數的數值,這是一種非常不好的行為。
3) reinterpret_cast
在C++語言中,reinterpret_cast主要有三種強制轉換用途:改變指標或引用的型別、將指標或引用轉換為一個足夠長度的整形、將整型轉換為指標或引用型別。
用法:reinterpret_cast<type_id> (expression)
type-id必須是一個指標、引用、算術型別、函式指標或者成員指標。
它可以把一個指標轉換成一個整數,也可以把一個整數轉換成一個指標(先把一個指標轉換成一個整數,在把該整數轉換成原型別的指標,還可以得到原先的指標值)。
在使用reinterpret_cast強制轉換過程僅僅只是位元位的拷貝,因此在使用過程中需要特別謹慎!
例7:
int *a = new int; double *d = reinterpret_cast<double *>(a);
在例7中,將整型指標通過reinterpret_cast強制轉換成了雙精度浮點型指標。
reinterpret_cast可以將指標或引用轉換為一個足夠長度的整形,此中的足夠長度具體長度需要多少則取決於作業系統,如果是32位的作業系統,就需要4個位元組及以上的整型,如果是64位的作業系統則需要8個位元組及以上的整型。
4) dynamic_cast
用法:dynamic_cast<type_id> (expression)
(1)其他三種都是編譯時完成的,dynamic_cast是執行時處理的,執行時要進行型別檢查。
(2)不能用於內建的基本資料型別的強制轉換。
(3)dynamic_cast轉換如果成功的話返回的是指向類的指標或引用,轉換失敗的話則會返回NULL。
(4)使用dynamic_cast進行轉換的,基類中一定要有虛擬函式,否則編譯不通過。
B中需要檢測有虛擬函式的原因:類中存在虛擬函式,就說明它有想要讓基類指標或引用指向派生類物件的情況,此時轉換才有意義。
這是由於執行時型別檢查需要執行時型別資訊,而這個資訊儲存在類的虛擬函式表(關於虛擬函式表的概念,詳細可見<Inside c++ object model>)中,
只有定義了虛擬函式的類才有虛擬函式表。
(5)在類的轉換時,在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是一樣的。在進行下行轉換時,dynamic_cast具有型別檢查的功能,比static_cast更安全。
向上轉換,即為子類指標指向父類指標(一般不會出問題);向下轉換,即將父類指標轉化子類指標。
向下轉換的成功與否還與將要轉換的型別有關,即要轉換的指標指向的物件的實際型別與轉換以後的物件型別一定要相同,否則轉換失敗。
在C++中,編譯期的型別轉換有可能會在執行時出現錯誤,特別是涉及到類物件的指標或引用操作時,更容易產生錯誤。Dynamic_cast操作符則可以在執行期對可能產生問題的型別轉換進行測試。
例1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
本例中定義了兩個類:base類和derived類,這兩個類構成繼承關係。在base類中定義了m函式,derived類中定義了f函式。在前面介紹多型時,我們一直是用基類指標指向派生類或基類物件,而本例則不同了。
本例主函式中定義的是一個派生類指標,當我們將其指向一個基類物件時,這是錯誤的,會導致編譯錯誤。
但是通過強制型別轉換我們可以將派生類指標指向一個基類物件,p = static_cast<derived *>(new base);語句實現的就是這樣一個功能,這樣的一種強制型別轉換時合乎C++語法規定的,但是是非常不明智的,它會帶來一定的危險。
在程式中p是一個派生類物件,我們將其強制指向一個基類物件,首先通過p指標呼叫m函式,因為基類中包含有m函式,這一句沒有問題,之後通過p指標呼叫f函式。一般來講,因為p指標是一個派生類型別的指標,而派生類中擁有f函式,因此p->f();這一語句不會有問題,但是本例中p指標指向的確實基類的物件,而基類中並沒有宣告f函式,雖然p->f();這一語句雖然仍沒有語法錯誤,但是它卻產生了一個執行時的錯誤。換言之,p指標是派生類指標,這表明程式設計人員可以通過p指標呼叫派生類的成員函式f,但是在實際的程式設計過程中卻誤將p指標指向了一個基類物件,這就導致了一個執行期錯誤。
產生這種執行期的錯誤原因在於static_cast強制型別轉換時並不具有保證型別安全的功能,而C++提供的dynamic_cast卻能解決這一問題,dynamic_cast可以在程式執行時檢測型別轉換是否型別安全。
當然dynamic_cast使用起來也是有條件的,它要求所轉換的運算元必須包含多型類型別(即至少包含一個虛擬函式的類)。
例2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
在本例中利用dynamic_cast進行強制型別轉換,但是因為base類中並不存在虛擬函式,因此p = dynamic_cast<derived *>(new base);這一句會編譯錯誤。
為了解決本例中的語法錯誤,我們可以將base類中的函式m宣告為虛擬函式,virtual void m(){cout<<"m"<<endl;}。
dynamic_cast還要求<>內部所描述的目標型別必須為指標或引用。
例3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
|