文章目錄
- C語言中的類型轉換
-
- 隐式類型轉換
- 顯式類型轉換
- C++ 強制類型轉換
-
- static_cast
- reinterpret_cast
- const_cast
- dynamic_cast
- explicit
C語言中的類型轉換
C語言中的類型轉換,通常發生在傳參時參數類型不比對,又或是接收的傳回值類型不一緻、指派雙方類型不同的情景,就會需要用到類型轉換。主要分為隐式類型轉換與顯示類型轉換兩種。
隐式類型轉換
編譯器在編譯階段自動進行,通常适用于相近的類型,如果不能轉換則會編譯失敗。
例如:
void print(int i)
{
cout << i << endl;
}
int main()
{
double d = 1.9;
print(d);
return 0;
}
顯式類型轉換
需要使用者自己處理,通常用于不相近類型的轉換。
如:
int main()
{
int i = 9;
int* p = &i;
//将指針轉換為位址
int addr = (int)p;
cout << addr;
}
從上面可以看出來,C語言的類型轉換使用起來很簡單,但是也有很大的缺點。
- 隐式類型轉換可能會因為整形提升或者資料截斷導緻精度的丢失,并且有時候會因為忽略隐式類型轉換導緻錯誤發生
- 顯示類型轉換代碼不夠清晰,沒有很好的将各種情況劃分開,而是全部混在一起使用
C++ 強制類型轉換
标準C++為了加強類型轉換的可視性,引入了四種命名的強制類型轉換操作符:static_cast、reinterpret_cast、const_cast、dynamic_cast
static_cast
static_cast用于非多态類型的轉換(靜态轉換),編譯器隐式執行的任何類型轉換都可用static_cast,但它不能用于兩個不相關的類型進行轉換。(即對應C語言中的隐式類型轉換)
int main()
{
double d = 1.9;
int i = static_cast<int>(d);
cout << i;
}
reinterpret_cast
reinterpret_cast是一種較為危險的類型轉換,通常為操作數的位模式提供較低層次的重新解釋,用于将一種類型轉換為另一種不同的類型,通常适用于指針、引用、以及整數之間的類型轉換。
int main()
{
int i = 9;
int* p = &i;
double* p2 = reinterpret_cast<double*>(p);
cout << *p2 << ' ' << *p;
}
如上面所說,這種轉換十分容易導緻錯誤的發生,因為指針類型其實是其指向的位址的類型,決定了指針看待這段位址的方式,它該如何讀取資料。這裡我把int改成了double,使得他原本應該讀取4個位元組,而變成了8個位元組,就導緻了資料的變化。如果使用不當很容易會造成越界通路導緻程式崩潰。
const_cast
const_cast通常用于删除變量的const屬性
如果想要修改const變量的值,就需要用volatile來取消編譯器優化。因為const變量建立後會被放入寄存器中,隻有我們取const變量的位址時,他才會在記憶體中申請空間,而我們修改的是const變量在記憶體中的值,但是由于編譯器優化,當使用const變量時就會到寄存器中去取值,是以需要用volatile取消編譯器優化,讓其每次在記憶體中取值。
不加volatile時
int main()
{
const int ci = 10;
int* pi = const_cast<int*>(&ci); // 對應c語言強制類型轉換中去掉const屬性的(不相近類型)
*pi = 20;
cout << ci << ' ' << *pi;
}
加volatile時
int main()
{
volatile const int ci = 10;
int* pi = const_cast<int*>(&ci); // 對應c語言強制類型轉換中去掉const屬性的(不相近類型)
*pi = 20;
cout << ci << ' ' << *pi;
}
dynamic_cast
dynamic_cast是一種動态的類型轉換,是C++新增的概念,用于将一個父類對象的指針/引用轉換為子類對象的指針或引用。
派生類可以指派給基類的對象、指針或者引用,這樣的指派也叫做對象切割。
例如Human類和Student類
從這幅圖可以看出來,當把派生類指派給基類時,可以通過切割掉多出來的成員如_stuNum的方式來完成指派。
但是基類對象如果想指派給派生類,則不可以,因為他不能憑空多一個_stuNum成員出來。
但是基類的指針或者引用卻可以強制類型轉換指派給派生類對象, 如:
這個過程有可能成功,也有可能會因為越界導緻出現問題。 如果使用C語言的強制類型轉換,很可能就會出現問題,因為其沒有安全保障。而如果使用dynamic_cast,則能夠保證安全,因為其會先檢查轉換是否能夠成功,如果不能成功則傳回0,能則直接轉換。
但是dynamic_cast的向下轉換隻支援繼承中的多态類型,也就是父類之中必須包含虛函數。
int main()
{
Human h1;
Student s1;
Human* hPtr1 = &s1;//指向派生類對象
Human* hPtr2 = &h1;//指向基類對象
//傳統方法
Student* pPtr = (Student*)hPtr1;//沒問題
Student* pPtr = (Student*)hPtr2;//有時候沒有問題,但是會存在越界風險
//dynamic_cast
Student* pPtr = dynamic_cast<Student*>(hPtr2);
return 0;
}
注意:
- dynamic_cast隻能用于含有虛函數的類
- dynamic_cast會先檢查是否能轉換成功,能成功則轉換,不能則傳回0
dynamic_cast是如何識别父類的指針指向的是父類對象還是子類對象的呢?其原理就是在運作時通過查找虛函數表上面的辨別資訊,來确認其指向的到底是父類還是子類,這也就是為什麼隻能用于含有虛函數的類。
這種在運作中進行類型識别的方法,也叫做RTTI,C++中有很多支援RTTI的方法,如dynamic_cast,typeid,decltype
explicit
explicit關鍵字主要用來阻止轉換構造函數而進行隐式類型轉換的發生
class Date
{
public:
Date(int year = 0, int month = 4, int day = 24)
:_year(year),
_month(month),
_day(day)
{}
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2020, 4, 24);
Date d2 = 2020;//C++98
Date d3 = { 2020, 4 }; //C++11
Date d4 = { 2020, 4, 24 }; //C+11
}
對于這裡的d2,我們用2020給它指派,而d3和d4分别用了清單來給它指派,并且這四個對象它們最後的值是一模一樣的,那是為什麼呢?這裡的2020明明是一個整型,d3和d4是一個清單,為什麼能夠給對象指派呢?
這裡就牽扯到了隐式的類型轉換
這裡其實是先用這個整型值來調用了全預設的構造函數來建立了一個臨時對象,再使用這個對象來為d2,d3,d4指派。
這是一種很容易引起誤會的寫法,是以c++提供了關鍵字explicit,用這個關鍵字修飾的函數就會禁止隐式類型的轉化
這時這種隐式類型轉換就不會發生了