目錄
一、C語言中的類型轉換
二、為什么C++要新的轉換格式
三、 C++強制類型轉換
1.static_cast
2.reinterpret_cast?
3.const_cast
4.dynamic_cast?
一、C語言中的類型轉換
在C語言中,如果賦值運算符左右兩側類型不同,或者形參與實參類型不匹配,或者返回值類型與接收返回值類型不一致時,就需要發生類型轉化,C語言中總共有兩種形式的類型轉換:隱式類型轉換和顯式類型轉換。
- ?隱式類型轉化:編譯器在編譯階段自動進行,能轉就轉,不能轉就編譯失敗
- 顯式類型轉化:需要用戶自己處理
void Test () {int i = 1;int* p = &i;// 隱式類型轉換double d = i;printf("%d, %.2f\n" , i, d);// 顯示的強制類型轉換int address = (int) p;printf("%x, %d\n" , p, address); }
注意:一般關聯性強,表示的意義相近的變量可以隱式轉換,如int轉換成double;一般關聯性不強的一些內置類型可以顯示轉換,比如(void*)int轉換成void*;沒有關聯性的幾乎不能轉換。
缺陷:轉換的可視性比較差,所有的轉換形式都是以一種相同形式書寫,難以跟蹤錯誤的轉換
二、為什么C++要新的轉換格式
?C風格的轉換格式很簡單,但是有不少缺點的:
- 隱式類型轉化有些情況下可能會出問題:比如數據精度丟失
- 顯式類型轉換將所有情況混合在一起,代碼不夠清晰
我們舉個例子。
void insert(size_t pos, char ch) {int end = 10;while (end >= pos){cout << end << endl;--end;} }int main() {insert(5, 'x');return 0; }
這段代碼正常插入沒有問題,但當pos是0時,就會陷入“死循環”。
因為size_t是無符號整形,--變成-1,其實是size_t表示的最大無符號整數。
針對這些問題,C++提出了自己的類型轉化風格,因為C++要兼容C語言,所以C++中還可以使用C語言的轉化風格。
三、 C++強制類型轉換
標準C++為了加強類型轉換的可視性,引入了四種命名的強制類型轉換操作符:static_cast、reinterpret_cast、const_cast、dynamic_cast 。
1.static_cast
?static_cast用于非多態類型的轉換(靜態轉換),編譯器隱式執行的任何類型轉換都可用static_cast,但它不能用于兩個不相關的類型進行轉換。
int main()
{double d = 12.34;int a = static_cast<int>(d);cout<<a<<endl;return 0;
}
2.reinterpret_cast?
reinterpret_cast操作符通常為操作數的位模式提供較低層次的重新解釋,用于將一種類型轉換為另一種不同的類型。
int main()
{double d = 12.34;int a = static_cast<int>(d);cout << a << endl;// 這里使用static_cast會報錯,應該使用reinterpret_cast//int *p = static_cast<int*>(a);int *p = reinterpret_cast<int*>(a);return 0;
}
3.const_cast
const_cast最常用的用途就是刪除變量的const屬性,方便賦值
const_cast轉換中有一個細節需要注意,?請看下面代碼,
int main()
{const int a = 2;int* p = const_cast<int*>(&a);*p = 3;cout << a << endl;cout << *p << endl;cout << &a << endl;cout << p << endl;return 0;
}
執行,
我們發現a的地址和p的值一樣,也就是p指向的空間就是a的值,可為什么p指向空間的值是3,a的值還是2呢,我們修改了*p,a的值也應該被修改了呀,a為什么沒有變?
因為const修飾的變量,編譯器會進行優化。正常const修飾的變量不會被修改,不會被修改就不用經常去內存中取,所以編譯器會把const修飾的值拷貝到寄存器,甚至用常量去替代,所以我們正常修改const_cast強制類型轉換“去const屬性的指針”,不會修改到內存中的const變量。
要想禁止編譯器對const變量的優化,可以在const前加一個關鍵字——volatile。
cv (const and volatile) type qualifiers - cppreference.com
?這樣我們運行,就可以修改到內存中a的值了,那么為什么a的地址是1呢?這是流插入(<<)的bug。
注意:流插入<<是指將程序中的數據插入到流中,輸出到屏幕或文件中或者其他流中;流提取>>是從流中提取數據到程序中。
我們可以用printf打印看一下,
printf打印就沒有問題,難道是類型識別錯誤,我們打印一下&a的類型。
?發現&a的類型是int const valatile*,
ostream::operator<< - C++ Reference (cplusplus.com)
可能編譯器類型匹配錯了,應該要匹配成指針類型的模板參數。我們強制轉換成指針類型看一下,
int* 和void*都可以,看來就是模板匹配錯誤。
?其實流插入還有一個缺點,就是當我們想打印字符的地址時,operator會匹配到字符類型的參數,從而打印的是字符而不是字符地址。
所以這里也必須強轉一下,?以上三種轉換都是C++對C語言轉換的規范。
4.dynamic_cast?
dynamic_cast用于將一個父類對象的指針/引用轉換為子類對象的指針或引用(動態轉換)
- 向上轉型:子類對象指針/引用->父類指針/引用(不需要轉換,賦值兼容規則)
class A { public:virtual void f() {}int _a = 0; }; class B : public A { public:int _b = 1; }; int main() {B objb;A obja = objb;A& ra = objb;//直接將objb繼承的部分切割給ra,中間沒有類型轉換double d = 1.1;const int& i = d;//隱式類型轉換,中間產生臨時變量,要const修飾return 0; }
- 向下轉型:父類對象指針/引用->子類指針/引用(用dynamic_cast轉型是安全的)
父類的指針或引用,轉換成子類的指針和引用,如果再用子類指針或引用訪問子類獨有的成員變量,就會造成越界訪問,如下:
class A { public:virtual void f() {}int _a = 0; }; class B : public A { public:int _b = 1; }; void fun(A* pa) {// 向下轉換:直接轉換是不安全的// 如果pa是指向父類A對象,存在越界問題B* ptr = (B*)pa;ptr->_a++;ptr->_b++;//越界訪問 }int main() {// 向下轉換規則:父類對象不能轉換成子類對象,但是父類指針和引用可以轉換子類指針和引用A a;B b;fun(&a);fun(&b);return 0; }
為了防止這類越界,C++推出了dynamic_cast用于將一個父類指針或引用轉換。
dynamic_cast conversion - cppreference.com
class A
{
public:virtual void f() {}int _a = 0;
};class B : public A
{public:int _b = 1;
};void fun(A* pa)
{// 向下轉換:直接轉換是不安全的// 如果pa是指向父類A對象,存在越界問題B* ptr = dynamic_cast<B*>(pa);if (ptr){ptr->_a++;ptr->_b++;}else{cout << "轉換失敗" << endl;}
}int main()
{// 向下轉換規則:父類對象不能轉換成子類對象,但是父類指針和引用可以轉換子類指針和引用A a;B b;fun(&a);fun(&b);return 0;
}
調試一下可以看到,向上轉換時,程序輸出,
對于指針類型向上轉換會返回空指針,對于引用類型向上轉換dynamic_cast會拋異常std::bad_cast?。
當然,這有一個前提,“表達式是對多態類型 Base 的指針或引用,并且 target-type 是對 Derived 類型的指針或引用,則執行運行時檢查”。
四、RTTI
RTTI:Run-time Type identification的簡稱,即:運行時類型識別。
五、參考
ttps://blog.csdn.net/hp_cpp/article/details/104095700
RTTI、虛函數和虛基類的實現方式、開銷分析及使用指導 (baiy.cn)