文章目錄
- C語言類型轉換
- 隱式類型轉換
- 顯式類型轉換
- C++ 強制類型轉換
- static_cast
- reinterpret_cast
- const_cast
- dynamic_cast
- 類的隱式類型轉換
- 概念
- 只允許一步類類型轉換
- explicit 抑制構造函數定義地隱式轉換
- 可以通過顯式轉換使用explicit構造函數
C語言類型轉換
隱式類型轉換
編譯器在編譯階段自動進行,通常適用于相近的類型,如果不能轉換則會編譯失敗。
何時發生隱式類型轉換:
- 在大多數表達式中,比int類型小的整數值提升為較大的整數類型
- 在條件中,非布爾值轉換成布爾類型
- 初始化過程中,初始值轉換成變量的類型;在賦值語句中,右側運算對象轉化成左側運算對象的類型
- 算術運算或關系運算對象有多種類型,需要轉換成同一種類型
- 函數調用時也會發生類型轉換
顯式類型轉換
需要用戶自己處理,通常用于不相近類型的轉換。
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;
}
可以看出 ci
的 const屬性
仍在,其值并未更改,只改了 *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* hPtrs = &s1; // 指向派生類對象Human* hPtrh = &h1; // 指向基類對象//傳統方法Student* pPtr = (Student*)hPtrs; // 沒問題Student* pPtr = (Student*)hPtrh; // 有時候沒有問題,但是會存在越界風險//dynamic_castStudent* pPtr = dynamic_cast<Student*>(hPtrh);return 0;
}
dynamic_cast
是如何識別父類的指針指向的是父類對象還是子類對象的呢?
其原理就是在運行時通過查找虛函數表上面的標識信息,來確認其指向的到底是父類還是子類,這也就是為什么只能用于含有虛函數的類。
這種在 運行中進行類型識別的方法 ,也叫做 RTTI
,C++中有很多支持 RTTI
的方法,如 dynamic_cast
、typeid
、decltype
。
類的隱式類型轉換
概念
類的隱式類型轉換: 如果類有需要 一個實參 的構造函數(或者構造函數雖然有 N
個參數,但這些參數都有缺省值),那么可以使用 該實參類型 構造一個 具有常量屬性的臨時類。這樣的行為看起來像從 實參類型 到 類類型 的隱式轉換。
示例如下:
class People{int age;string name;
public:People(int a){age = a;name = "zhangsan";}People(string b){age = 10;name = b;}People(){age = 21;name = "lihua";}People& add(const People &temp){age += temp.age;return *this;}People& revise(const People &temp){name = temp.name;return *this;}int getage(){return this->age;}string getname(){return this->name;}
};
int main()
{int num = 30;string who = "zhaoliu";People p1,p2(12);p1.add(num);cout << p1.getage() << endl;p2.revise(who);cout << p2.getname() << endl;
}
輸出結果:
在 People
類中,接收 int
和 string
的構造函數分別運用了People類類型的隱式轉換機制(即分別定義了從 int
、string
類型向 People
隱式轉化的規則)。換言之,在需要People的地方,我們可以使用string或者int作為替代。
在上述代碼main函數中:
int num = 30;
p1.add(num);
// 用num構造一個臨時的People對象,該對象的name為空串,age等于30
我們用int實參充當了add的成員。該操作是合法的,編譯器用給定的int自動創建了一個People對象。新生成的這個臨時People對象被傳遞給add。因為add的參數是一個常量引用,所以我們可以給該參數傳遞一個臨時量。(因為臨時量具有常量屬性)
那如果add的參數不是常量引用,以上操作還可以進行嗎?
答案是否定的,因為num構造臨時的people經過了形如下式的轉化:
const People temp = People(num);
之后是將temp臨時量作為實參傳入add函數
p1.add(temp)
而如若add的形參只是一個普通引用的話,是無法綁定具有常量屬性的臨時量temp的。
只允許一步類類型轉換
編譯器只會自動地執行一步類型轉換。
下面的調用是錯誤的:
p2.revise("huangba");
// error:隱式地使用了兩種轉換規則
// (1)把"huangba"轉換成string
// (2)再把臨時的string轉換成People
如果想完成上述調用,可以顯式地將字符串轉換為string或者People對象:
// 顯式地轉換成string,隱式地轉化成Peoplep2.revise(string("huangba"));// 顯式地轉換成People,隱式地轉化成stringp2.revise(People("huangba"));
explicit 抑制構造函數定義地隱式轉換
我們之前提到,如果構造函數有多個參數,但這些參數都有默認實參,那么同樣遵循類的隱式轉換規則:
class Date
{
public:Date(int year, 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++98Date d3 = { 2020, 5 }; //C++11Date d4 = { 2020, 5, 26 }; //C+11
}
這里其實是先用這個整型值來調用了缺省的構造函數來創建了一個臨時對象,再使用這個對象來為 d2、d3、d4
賦值 。
如果想要禁止隱式轉換,可以通過將構造函數聲明為 explicit
加以阻止:
此時,沒有任何構造函數能用于隱式地創建 Date 對象,隱式轉換就不會發生了。
- 關鍵字
explicit
只對需要一個實參的構造函數有效,需要多個實參的構造函數不能用于執行隱式轉換,所以無須將這些構造函數指定為explicit
。 - 只能在類內聲明構造函數時使用explicit關鍵字,在類外部定義時不應重復。
explicit
構造函數只能用于 直接初始化 ,而且,編譯器不會在自動轉換過程中使用該構造函數。如下:
可以通過顯式轉換使用explicit構造函數
編譯器不會將具有 explicit
屬性的構造函數用于隱式轉換過程,但是我們可以顯式地進行轉換(強制類型轉換):