前言
在C語言中,我們需要做類型轉換時,常常就是簡單粗暴,在C++中也可以用C式強制類型轉換,但是C++有它自己的一套類型轉換方式。
C式的顯示類型轉換
先來說說C式的強制類型轉換,它的用法非常簡單,形如下面這樣
Type b =111;
Typea a = (Typea)b;
只需要用括號將你要轉換的類型擴起來,放在要轉換的變量前面即可。
舉個例子:
#include<stdio.h>
intmain(void)
{
inta =0x01020304;
char*b = (char*)&a;
inti =0;
for(;i <4;i++)
{
printf("%02x\\n",b[i]);
}
return0;
}
編譯運行輸出結果:
04
03
02
01
如果你好奇為什麼會是這樣的結果,請參考《字節序的那些事》。
C++ 四種強制類型轉換。
當然,C++也是支持C風格的強制轉換,但是C風格的強制轉換可能帶來一些隱患,讓一些問題難以察覺.所以C++提供了一組可以用在不同場合的強制轉換的函數。
const_cast , static_cast , dynamic_cast , reinterpret_cast
const_cast
常量指針被轉化成非常量的指針,並且仍然指向原來的對象;
常量引用被轉換成非常量的引用,並且仍然指向原來的對象;
const_cast一般用於修改指針。如const char *p形式。
#include<iostream>
intmain()
{
// 原始數組
intary[4] = {1,2,3,4};
// 打印數據
for(inti =0; i <4; i++)
std::cout<< ary[i] <
std::cout<
// 常量化數組指針
constint*c_ptr = ary;
//c_ptr[1] = 233; //error
// 通過const_cast
int*ptr =const_cast(c_ptr);
// 修改數據
for(inti =0; i <4; i++)
ptr[i] +=1;//pass
// 打印修改後的數據
for(inti =0; i <4; i++)
std::cout<< ary[i] <
std::cout<
return0;
}
/* out print
1 2 3 4
2 3 4 5
*/
注意:對於在定義為常量的參數,使用const_cast可能會有不同的效果.類似代碼如下
#include<iostream>
intmain(){
constintc_val =233;//聲明為常量類型
int&use_val =const_cast(c_val);//使用去const 引用
int*ptr_val =const_cast(&c_val);//使用去const 指針
use_val =666;//未定義行為
std::cout<< c_val <
*ptr_val =110;//未定義行為
std::cout<< c_val <
return0;
}
未定義行為:C++標準對此類行為沒有做出明確規定.同一份代碼在使用不同的編譯器會有不同的效果.在 vs2017 下,,雖然代碼中 c_val , use_val , ptr_val 看到的地址是一樣的.但是c_val的值並沒有改變.有可能在某種編譯器實現後,這一份代碼的c_val 會被改變.也有可能編譯器對這類行為直接 error 或 warning.
static_cast
static_cast 作用和C語言風格強制轉換的效果基本一樣,由於沒有運行時類型檢查來保證轉換的安全性,所以這類型的強制轉換和C語言風格的強制轉換都有安全隱患。
用於類層次結構中基類(父類)和派生類(子類)之間指針或引用的轉換。注意:進行上行轉換(把派生類的指針或引用轉換成基類表示)是安全的;進行下行轉換(把基類指針或引用轉換成派生類表示)時,由於沒有動態類型檢查,所以是不安全的。
用於基本數據類型之間的轉換,如把int轉換成char,把int轉換成enum。這種轉換的安全性需要開發者來維護。
static_cast不能轉換掉原有類型的const、volatile、或者 __unaligned屬性。(前兩種可以使用const_cast 來去除)
在c++ primer 中說道:任何具有明確定義的類型轉換,只要不包含const,都可以使用static_cast。
/* 常規的使用方法 */
floatf_pi=3.141592f
inti_pi=static_cast(f_pi);/// i_pi 的值為 3
/* class 的上下行轉換 */
classBase{
// something
};
classSub:publicBase{
// something
}
// 上行 Sub -> Base
//編譯通過,安全
Sub sub;
Base *base_ptr =static_cast(&sub);
// 下行 Base -> Sub
//編譯通過,不安全
Base base;
Sub *sub_ptr =static_cast(&base);
dynamic_cast
dynamic_cast強制轉換,應該是這四種中最特殊的一個,因為他涉及到面向對象的多態性和程序運行時的狀態,也與編譯器的屬性設置有關.所以不能完全使用C語言的強制轉換替代,它也是最常有用的,最不可缺少的一種強制轉換.
#include<iostream>
usingnamespacestd;
classBase{
public:
Base() {}
~Base() {}
voidprint(){
std::cout<
}
virtualvoidi_am_virtual_foo(){}
};
classSub:publicBase{
public:
Sub() {}
~Sub() {}
voidprint(){
std::cout<
}
virtualvoidi_am_virtual_foo(){}
};
intmain(){
cout<Base"<
Sub * sub =newSub();
sub->print();
Base* sub2base =dynamic_cast(sub);
if(sub2base !=nullptr) {
sub2base->print();
}
cout<base> sub2base val is: "<< sub2base <
cout<
Base *base =newBase();
base->print();
Sub *base2sub =dynamic_cast(base);
if(base2sub !=nullptr) {
base2sub->print();
}
cout<sub> base2sub val is: "<< base2sub <
deletesub;
deletebase;
return0;
}
/* vs2017 輸出為下
Sub->Base
I'm Sub
I'm Base
base> sub2base val is: 00B9E080 // 注:這個地址是系統分配的,每次不一定一樣
Base->Sub
I'm Base
sub> base2sub val is: 00000000 // VS2017的C++編譯器,對此類錯誤的轉換賦值為nullptr
*/
從上邊的代碼和輸出結果可以看出:
對於從子類到基類的指針轉換 ,dynamic_cast 成功轉換,沒有什麼運行異常,且達到預期結果
而從基類到子類的轉換 , dynamic_cast 在轉換時也沒有報錯,但是輸出給 base2sub 是一個 nullptr ,說明dynami_cast 在程序運行時對類型轉換對“運行期類型信息”(Runtime type information,RTTI)進行了檢查.
這個檢查主要來自虛函數(virtual function) 在C++的面對對象思想中,虛函數起到了很關鍵的作用,當一個類中擁有至少一個虛函數,那麼編譯器就會構建出一個虛函數表(virtual method table)來指示這些函數的地址,假如繼承該類的子類定義並實現了一個同名並具有同樣函數簽名(function siguature)的方法重寫了基類中的方法,那麼虛函數表會將該函數指向新的地址。此時多態性就體現出來了:當我們將基類的指針或引用指向子類的對象的時候,調用方法時,就會順著虛函數表找到對應子類的方法而非基類的方法。因此注意下代碼中 Base 和 Sub 都有聲明定義的一個虛函數 ” i_am_virtual_foo” ,我這份代碼的 Base 和 Sub 使用 dynami_cast 轉換時檢查的運行期類型信息,可以說就是這個虛函數
reinterpret_cast
reinterpret_cast是強制類型轉換符用來處理無關類型轉換的,通常為操作數的位模式提供較低層次的重新解釋!但是他僅僅是重新解釋了給出的對象的比特模型,並沒有進行二進制的轉換!
他是用在任意的指針之間的轉換,引用之間的轉換,指針和足夠大的int型之間的轉換,整數到指針的轉換。
請看一個簡單代碼
#include<iostream>
#include<cstdint>
usingnamespacestd;
intmain(){
int*ptr =newint(233);
uint32_tptr_addr =reinterpret_cast(ptr);
cout<
<
deleteptr;
return0;
}
/*
ptr 的地址: 0061E6D8
ptr_addr 的值(hex): 0061e6d8
*/
上述代碼將指針ptr的地址的值轉換成了 unsigned int 類型的ptr_addr 的整數值.
提供下IBM C++ 對 reinterpret_cast 推薦使用的地方
A pointer to any integral type large enough to hold it (指針轉向足夠大的整數類型)
A value of integral or enumeration type to a pointer (從整形或者enum枚舉類型轉換為指針)
A pointer to a function to a pointer to a function of a different type (從指向函數的指針轉向另一個不同類型的指向函數的指針)
A pointer to an object to a pointer to an object of a different type (從一個指向對象的指針轉向另一個不同類型的指向對象的指針)
A pointer to a member to a pointer to a member of a different class or type, if the types of the members are both function types or object types (從一個指向成員的指針轉向另一個指向類成員的指針!或者是類型,如果類型的成員和函數都是函數類型或者對象類型)
下面這個例子來自 MSDN 的一個哈希函數輔助
// expre_reinterpret_cast_Operator.cpp
// compile with: /EHsc
#include<iostream>
// Returns a hash code based on an address
unsignedshortHash(void*p){
unsignedintval =reinterpret_cast(p);
return(unsignedshort)(val ^ (val >>16));
}
usingnamespacestd;
intmain(){
inta[20];
for(inti =0; i <20; i++)
cout<< Hash(a + i) <
}
結尾
在使用強制轉換的時候,請先考慮清楚我們真的需要使用強制轉換和我們應該使用那種強制轉換.
我這只是簡單的介紹這四種強制轉換的用途,以上是自己的理解,有任何問題請在下方發表你的看法哦!
謝謝閱讀.
“全球最大的C/C++人員聚集地就在我這裡,企鵝裙餿直播編程學習,【14】就是我的。
不管你是什麼基礎,來了就是兄弟,是兄弟就跟我一起學習C/C++!關注我,為編程點贊,每天學點小知識!工作需要、感興趣、為了入行、轉行需要學習C/C++的夥伴可以跟我學習,技術大牛、學習資料等助你早日成為一名優秀的程序員!
閱讀更多 虎牙來了 的文章