1. 程式人生 > 實用技巧 >【C++】型別轉換

【C++】型別轉換

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)