C++類型轉換主要分為兩種:隱式類型轉換、顯式類型轉換(強制類型轉換)。
【1】隱式類型轉換
所謂隱式類型轉換,是指不需要用戶干預,編譯器默認進行的類型轉換行為(很多時候用戶可能都不知道到底進行了哪些轉換)。
隱式類型轉換一般分為兩種:內置數據類型、自定義數據類型。
[1.1] 內置數據類型(基本數據類型)
例1:混合類型的算術運算表達式中
1 int nValue = 8; 2 double dValue = 10.7; 3 double dSum = nValue + dValue; // nValue會被自動轉換為double類型,用轉換的結果再與dValue相加
例2:不同類型的賦值操作時
1 int nValue = true; // bool類型被轉換為int類型
例3:函數參數傳值時
1 void func(double dArg); // 聲明函數 2 func(1); // 調用函數。整型數值1被轉換為double類型數值1.0
例4:函數返回值時
1 double add(int na, int nb) 2 { 3 return (na + nb); // 運算結果會被隱式轉換為double類型返回 4 }
以上各種情況的隱式類型轉換,都滿足了一個基本原則:由低精度向高精度的轉換。
若不滿足該原則,編譯器會提示編譯警告。如下:
1 double dValue = 100.2; 2 int nValue = dValue; // : warning C4244: “初始化”: 從“double”轉換到“int”,可能丟失數據
當然,這時我們若不想看到警告,可以選擇顯式類型轉換(又稱強制類型轉換)。如下:
1 double dValue = 100.2; 2 int nValue = (int)dValue;
[1.2] 自定義數據類型
隱式類型轉換的風險一般存在於自定義類型轉換間。尤其需要注意自定義類的構造函數。例如:
1 class MyString 2 { 3 public: 4 MyString(int n) {}; // 本意:預先分配n個字節給字符串 5 MyString(const char* p) {}; // 用C風格的字符串p作為初始化值 6 // ...... 7 }; 8 9 void main() 10 { 11 MyString s1 = "China"; //OK 隱式轉換,等價於MyString s1 = MyString(”China”) 12 MyString s2(10); // OK 分配10個字節的空字符串 13 MyString s3 = MyString(10); // OK 分配10個字節的空字符串 14 15 MyString s4 = 10; // OK,編譯通過。也是分配10個字節的空字符串 16 MyString s5 = 'A'; // 編譯通過。分配int('A')個字節的空字符串 17 // s4 和s5 分別把一個int型和char型,隱式轉換成了分配若干字節的空字符串,容易令人誤解。 18 }
如上例,要想禁止此種隱式類型轉換,可以使用C++關鍵字explicit(詳細請參見隨筆《explicit關鍵字》)。
【2】顯式類型轉換(強制類型轉換)
四種強制類型轉換操作符:static_cast、const_cast、dynamic_cast、reinterpret_cast
[2.1] static_cast
(1)主要用於內置數據類型之間的相互轉換。
1 double dValue = 12.12; 2 float fValue = 3.14; // VS2013 warning C4305: “初始化”: 從“double”到“float”截斷 3 int nDValue = static_cast<int>(dValue); // 12 4 int nFValue = static_cast<int>(fValue); // 3
(2)也可以轉換自定義類型。如果涉及到類,static_cast只能在有相互聯系(繼承)的類型間進行轉換,且不一定包含虛函數。
1 class A 2 {}; 3 4 class B : public A 5 {}; 6 7 class C 8 {}; 9 10 void main() 11 { 12 A *pA = new A; 13 B *pB = static_cast<B*>(pA); // 編譯不會報錯, B類繼承於A類 14 pB = new B; 15 pA = static_cast<A*>(pB); // 編譯不會報錯, B類繼承於A類 16 C *pC = static_cast<C*>(pA); // 編譯報錯, C類與A類沒有任何關系。error C2440: “static_cast”: 無法從“A *”轉換為“C *” 17 }
(3)把void類型指針轉換成目標類型的指針(不安全)
[2.2] const_cast
關於操作符const_cast。詳細請參見隨筆《強制類型轉換const_cast》
[2.3] dynamic_cast
(1)其他三種都是編譯時完成的。dynamic_cast是運行時處理的,運行時要進行類型檢查。
(2)不能用於內置基本數據類型間的強制轉換。例如:
1 double dValue = 12.12; 2 int nDValue = dynamic_cast<int>(dValue); // error C2680 : “int” : dynamic_cast 的目標類型無效。目標類型必須是指向已定義類的指針或引用
(3)使用dynamic_cast進行轉換時,基類中一定要有虛函數,否則編譯不通過。
需要有虛函數的原因:類中存在虛函數,就說明它有想要讓基類指針或引用指向派生類對象的必要,此時轉換才有意義。
由於運行時類型檢查需要運行時類型信息,而這個信息存儲在類的虛函數表中,只有定義了虛函數的類才有虛函數表。
1 class A 2 {}; 3 4 class B : public A 5 {}; 6 7 class C 8 {}; 9 10 void main() 11 { 12 A *pA = new A; 13 B *pB = dynamic_cast<B*>(pA); // 編譯錯誤error C2683: “dynamic_cast”:“A”不是多態類型 14 }
(4)dynamic_cast轉換若成功,返回的是指向類的指針或引用;若失敗則會返回NULL。
(5)在類的轉換時,在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是一樣的。
在進行下行轉換時,dynamic_cast具有類型檢查的功能,比static_cast更安全。
向上轉換即為指向子類對象的向上轉換,即將子類指針轉化父類指針。
向下轉換的成敗取決於將要轉換的類型,即要強制轉換的指針所指向的對象實際類型與將要轉換后的類型一定要相同,否則轉換失敗。
關於(4)、(5)兩條的代碼示例如下:
1 #include <iostream> 2 #include <cstring> 3 using namespace std; 4 5 class A 6 { 7 public: 8 virtual void f() 9 { 10 cout << "A::f()" << endl; 11 } 12 }; 13 14 class B : public A 15 { 16 public: 17 void f() 18 { 19 cout << "B::f()" << endl; 20 } 21 22 void bf() 23 { 24 cout << "B::bf()" << endl; 25 } 26 }; 27 28 class C 29 { 30 void pp() 31 { 32 return; 33 } 34 }; 35 36 int fun() 37 { 38 return 1; 39 } 40 41 void main() 42 { 43 A* pAB = new B; // pAB是A類型的指針指向一個B類型的對象 44 A* pAA = new A; // pAA是A類型的指針指向一個A類型的對象 45 B* pB = nullptr; 46 C* pC = nullptr; 47 pB = dynamic_cast<B*>(pAB); // 結果為not nullptr,向下轉換成功,pAB指向的就是B類型的對象,所以可以轉換成B類型的指針。 48 if (nullptr == pB) 49 { 50 cout << "dynamic_cast :: nullptr" << endl; 51 } 52 else 53 { 54 cout << "dynamic_cast :: not nullptr" << endl; 55 } 56 // 等價於static_cast 57 pB = static_cast<B*>(pAB); // 結果為not nullptr,向下轉換成功,pAB指向的就是B類型的對象,所以可以轉換成B類型的指針。 58 if (nullptr == pB) 59 { 60 cout << "static_cast :: nullptr" << endl; 61 } 62 else 63 { 64 cout << "static_cast :: not nullptr" << endl; 65 } 66 67 pB = dynamic_cast<B*>(pAA); // 結果為nullptr,向下轉換失敗。pAA指向的是A類型的對象,所以無法轉換為B類型的指針。 68 if (nullptr == pB) 69 { 70 cout << "dynamic_cast :: nullptr" << endl; 71 } 72 else 73 { 74 cout << "dynamic_cast :: not nullptr" << endl; 75 } 76 77 // static_cast的不安全性測試 78 pB = static_cast<B*>(pAA); // 結果為not nullptr,向下轉換成功。pAA指向的是A類型的對象,竟然轉換為B類型的指針! 79 if (nullptr == pB) 80 { 81 cout << "static_cast :: nullptr" << endl; 82 } 83 else 84 { 85 cout << "static_cast :: not nullptr" << endl; // 不安全性 86 pB->f(); // A::f() 87 pB->bf(); // B::bf() 88 } 89 90 pC = dynamic_cast<C*>(pAB); // 結果為nullptr,向下轉換失敗。pAB指向的是B類型的對象,所以無法轉換為C類型的指針。 91 if (nullptr == pC) 92 { 93 cout << "dynamic_cast :: nullptr" << endl; 94 } 95 else 96 { 97 cout << "dynamic_cast :: not nullptr" << endl; 98 } 99 100 // pC = static_cast<C*>(pAB); 101 // error C2440: “static_cast”: 無法從“A *”轉換為“C *” 與指向的類型無關;轉換要求 reinterpret_cast、C 樣式轉換或函數樣式轉換 102 103 delete pAB; 104 delete pAA; 105 106 system("pause"); 107 } 108 // run out: 109 /* 110 dynamic_cast :: not nullptr 111 static_cast :: not nullptr 112 dynamic_cast :: nullptr 113 static_cast :: not nullptr 114 A::f() 115 B::bf() 116 dynamic_cast :: nullptr 117 */
由程序運行結果分析:static_cast的不安全性顯而易見。
1 pB = static_cast<B*>(pAA);
向下轉換結果為not nullptr。pAA指向的是A類型的對象,竟然可以轉換為B類型的指針!相當危險!
(6)使用dynamic_cast的類型轉換,其轉換結果幾乎都是執行期定義(implementation-defined)。因此,使用reinterpret_casts的代碼很難移植。
[2.4] reinterpret_cast
有着與C風格的強制轉換同樣的能力。
它可以轉化任何內置的數據類型為其他任何的數據類型,也可以轉化任何指針類型為其他的類型。
它甚至可以轉化內置的數據類型為指針,無須考慮類型安全或者常量的情形。不到萬不得已絕對不用。
【3】總結
(1)C風格是(type)expression
(2)C++風格是static_cast<type>(expression)
Good Good Study, Day Day Up.
順序 選擇 循環 總結