C++基礎入門篇之強制轉換
引言
假設有基類 A,包含瞭虛函數 func1,以及有派生類 B,繼承於類 A,派生類 B 中實現瞭函數 func1。此時可以用 A 類型的指針指向 B 類型的對象,並用 A 類型的指針調用 B 類型對象中的函數 func1。這時,就形成瞭多態。包含虛函數的類 A,我們也稱為多態類。
由於派生類 B 完整包含瞭 基類 A 的所有定義,將 B 類型的指針轉換為 A 類型的指針總是安全的。
而將 A 類型的指針強制轉換為 B 類型的指針時,如果 A 類型指針指向的對象確實為 B 類型的對象,那麼轉換也是安全的。此時,該 B 類型對象被稱為完整對象(complete object)。
強制轉換有哪些類型?
C++ 包含瞭以下幾種強制轉換運算符,這些運算符用於消除老式 C 語言轉換中的存在的歧義和隱患:
- dynamic_cast
- static_cast
- const_cast
- reinterpret_cast
- safe_cast
本文會著重介紹如何使用 dynamic_cast 和 static_cast。
提醒:
除非必須,不要使用 const_cast 和 reinterpret_cast,因為它們存在一些老式 C 語言轉換中的隱患。
dynamic_cast 運算符
語法:
dynamic_cast <type-id> (expression)
type-id 必須是一個指針或者引用,指向/引用已定義的類類型或者 void。如果type-id 是指針,則 expression 必須也為指針類型,如果 type-id 是引用,expression 必須為左值類型。
如果 type-id 是 void*,那麼在運行時將檢測 expression 的實際類型。其結果返回 expression 指向的完整對象。
如非需要,現代 C++ 中應該避免使用 void 指針,因為容易出錯。
下面看些示例,瞭解 dynamic_cast 的使用方式。
示例1:
class Root { }; class Base : public Root { }; class Derived : public Base { }; void f(Derived* pd) { Base* pb = dynamic_cast<Base*>(pd); // ok: Base is a direct base class // pb points to Base subobject of pd Root* pr = dynamic_cast<Root*>(pd); // ok: Root is an indirect base class // pr points to Root subobject of pd }
示例1 中提到瞭子對象(subobject)的概念,註意與子類型進行區分:
- Root 類型包含子類型 Base,Base 類型包含子類型 Derived。
- Derived 對象包含瞭 Base 類型的子對象,Base 類型的子對象又包含瞭 Root 類型的子對象。
再聯系下前面說的:派生類完整包含瞭基類的所有定義。
示例2:
class B {virtual void f();}; class D : public B {virtual void f();}; void f() { B* pb = new D; // unclear but ok B* pb2 = new B; D* pd = dynamic_cast<D*>(pb); // ok: pb actually points to a D D* pd2 = dynamic_cast<D*>(pb2); // pb2 was nullptr. }
示例2 中 通過 dynamic_cast 轉換為 pd2 時,不會報錯,返回 nullptr。但如果同樣地情況轉換的對象是引用類型,那麼運行時會拋出 std::bad_cast 異常。如果 pb2 指向/引用的對象無效,同樣也會拋出異常。
示例3:
#include <stdio.h> #include <iostream> struct A { virtual void test() { printf_s("in A\n"); } }; struct B : A { virtual void test() { printf_s("in B\n"); } void test2() { printf_s("test2 in B\n"); } }; struct C : B { virtual void test() { printf_s("in C\n"); } void test2() { printf_s("test2 in C\n"); } }; void Globaltest(A& a) { try { C &c = dynamic_cast<C&>(a); printf_s("in GlobalTest\n"); } catch(std::bad_cast) { printf_s("Can't cast to C\n"); } } int main() { A *pa = new C; A *pa2 = new B; pa->test(); B * pb = dynamic_cast<B *>(pa); if (pb) pb->test2(); C * pc = dynamic_cast<C *>(pa2); if (pc) pc->test2(); C ConStack; Globaltest(ConStack); // will fail because B knows nothing about C B BonStack; Globaltest(BonStack); } Output: in C test2 in B in GlobalTest Can't cast to C
static_cast 運算符
語法:
static_cast <type-id> (expression)
static_cast 通常用於數值類型轉換,例如枚舉和整型,整型和浮點類型的轉換。
在標準 C++ 中,static_cast 轉換沒有運行時檢測來保證安全性。在 C++/CX 中,則包含瞭編譯和運行時檢測。
static_cast 運算符能夠用於將基類指針轉換為派生類指針,但這樣的轉換不總是安全的。
下面還是通過示例進行講解。
示例1:
class B {}; class D : public B {}; void f(B* pb, D* pd) { D* pd2 = static_cast<D*>(pb); // Not safe, D can have fields // and methods that are not in B. B* pb2 = static_cast<B*>(pd); // Safe conversion, D always // contains all of B. }
示例1 中 pd2 不為空,當用指針 pd2 調用 B 類型對象不存在的方法或者成員時可能會發生運行時錯誤(比如調用虛函數)或者返回非預期的值。
示例2:
typedef unsigned char BYTE; void f() { char ch; int i = 65; float f = 2.5; double dbl; ch = static_cast<char>(i); // int to char dbl = static_cast<double>(f); // float to double i = static_cast<BYTE>(ch); }
示例2 中 static_cast 運算符顯示地將內置類型進行轉換。
關於 static_cast 運算符,還有以下幾種使用情況:
- static_cast 能夠顯式的將整型轉換為枚舉類型。如果整型值不在枚舉值范圍內,那麼返回的枚舉值是未定義的。
- static_cast 能將任何 expression 顯式地轉換為 void 類型。
- static_cast 操作符不會去除 const,volatile,__unaligned 屬性。
區分幾種強制轉換的使用場景
dynamic_cast 主要用於多態類型的強制轉換,而 static_cast 主要用於非多態類型的強制轉換。
static_cast 轉換不像 dynamic_cast 那樣安全。因為 static_cast 沒有運行時檢測。通過 dynamic_cast 進行轉換時,一旦存在歧義,就會導致失敗,然而 static_cast 會像沒有錯誤發生一樣返回結果。盡管 dynamic_cast 更加安全,但 dynamic_cast 僅適用於指針和引用,並且運行時檢測是需要消耗性能的。
示例:
class B { public: virtual void Test(){} }; class D : public B {}; void f(B* pb) { D* pd1 = dynamic_cast<D*>(pb); D* pd2 = static_cast<D*>(pb); }
如果 pb 實際指向類型 D 或者 pd == 0,那麼 pd1 和 pd2 將獲得相同的值。
如果 pb 實際指向類型 B,那麼 dynamic_cast 會返回 0。但是 static_cast 依賴於 expression 認定 pb 指向 D 類型對象,於是簡單的返回 D 類型的指針。
結果就是,static_cast 轉換會繼續執行,但其返回結果是未定義的。這就需要調用者去進一步驗證轉換結果是有效的。
引用
https://docs.microsoft.com/en-us/cpp/cpp/casting?view=msvc-160
總結
到此這篇關於C++基礎入門篇之強制轉換的文章就介紹到這瞭,更多相關C++強制轉換內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- C++強制類型轉換(static_cast、dynamic_cast、const_cast、reinterpret_cast)
- 淺析C++中dynamic_cast和static_cast實例語法詳解
- C++ 強制類型轉換詳解
- C++ RTTI與4種類型轉換的深入理解
- C++ 數據類型強制轉化的實現