關于C++中的類型轉換操作符

內容簡介

本文對四種標準C++類型轉換符: static_cast, dynamic_cast, reinterpret_cast, const_cast 進行介紹,通過本文應當能夠理解這四個類型轉換操作符的含義。

四種標準C++的類型轉換符:

  • dynamic_cast: 動態類型轉換,一般用在父類和子類指針或應用的互相轉化;
  • static_cast: 靜態類型轉換,一般是普通數據類型轉換(如 int m=static_cast(3.14));
  • reinterpret_cast: 重新解釋類型轉換,很像C的一般類型轉換操作;
  • const_cast: 常量類型轉換,是把 constvolatile 屬性去掉。

下面將依次對它們進行相對詳細地介紹。

static_cast

支持子類指針到父類指針的轉換,并根據實際情況調整指針的值,反過來也支持,但會給出編譯警告,它作用最類似C風格的“強制轉換”,一般來說可認為它是安全的。

用法: static_cast < type-id > ( expression )

功能

該運算符把 expression 轉換為 type-id 類型,但沒有運行時類型檢查來保證轉換的安全性。

描述

主要如下幾種用法:

  • 用于類層次結構中基類(父類)和派生類(子類)之間指針或引用的轉換

    • 進行上行轉換(把派生類的指針或引用轉換成基類表示是安全的);
    • 進行下行轉換(把基類指針或引用轉換成派生類表示時,由于沒動態類型檢查,所以是不安全的)。
  • 用于基本數據類型之間的轉換,如把 int 轉換成 char ,把 int 轉換成 enum

    這種轉換的安全性也要開發人員來保證。

  • 把空指針轉換成目標類型的空指針

  • 把任何類型的表達式轉換成 void 類型

注意: static_cast 不能轉換掉 expressionconst, volitale, 或者 __unaligned 屬性。

舉例

這里,關于 static_cast 的使用舉例,通過與 reinterpret_cast 的例子進行對比,容易理解,所以參見后面 reinterpret_cast 的使用舉例部分中對 static_cast 的使用方法。

dynamic_cast

用法: dynamic_cast < type-id > ( expression )

功能

該運算符把 expression 轉換成 type-id 類型的對象, Type-id 必須是類的指針、類的引用或者 void * 。

描述

  • 支持子類指針到父類指針的轉換,并根據實際情況調整指針的值。

    這個和 static_cast 不同,反過來它就不支持了(即不允許使用這個操作符號將父類強制轉換到子類),會導致編譯錯誤,所以這種轉換是最安全的轉換。

    如果 type-id 是類指針類型,那么 expression 也必須是一個指針,如果 type-id 是一個引用,那么 expression 也必須是一個引用。

注: dynamic_cast 主要用于類層次間的上行轉換和下行轉換,還可以用于類之間的交叉轉換。在類層次間進行上行轉換時, dynamic_caststatic_cast 的效果是一樣的;在進行下行轉換時, dynamic_cast 具有類型檢查的功能,比 static_cast 更安全。

舉例

在類層次間進行轉換

代碼如下:

class B{
public:
      int m_iNum;
      virtual void foo();
};

class D:public B{
   public:
      char *m_szName[100];
};

void func(B *pb){
   D *pd1 = static_cast<D *>(pb);
   D *pd2 = dynamic_cast<D *>(pb);
}

這里,使用 dynamic_cast 進行轉換,如果出現了把指向父類對象的指針轉換成了子類的指針的時候,就會返回空值。

在上面的代碼段中,如果 pb 指向一個 D 類型的對象, pd1pd2 是一樣的,并且對這兩個指針執行 D 類型的任何操作都是安全的;但是,如果 pb 指向的是一個 B 類型的對象,那么 pd1 將是一個指向該對象的指針,對它進行 D 類型的操作將是不安全的(如訪問 m_szName ),而 pd2 將是一個空指針。(大多數面向對象編程中,經常會在代碼中出現父類型的指針指向子類對象,然后在合適的時候轉換為子類對象類型,直接代碼中看不出指針所知對象究竟是父類還是子類,通過這個 dynamic_cast 可以檢查類型,達到安全性)。

另外要注意:如果使用 dynamic_cast, B (父類)要有虛函數,否則會編譯出錯; static_cast 則沒有這個限制。這是由于運行時類型檢查需要運行時類型信息,而這個信息存儲在類的虛函數表(關于虛函數表的概念,詳細可見 <Inside c++ object model> )中,只有定義了虛函數的類才有虛函數表,沒有定義虛函數的類是沒有虛函數表的。

類之間的交叉轉換

代碼如下:

class A{
public:
       int m_iNum;
       virtual void f(){}
};

class B:public A{
};

class D:public A{
};

void foo(){
   B *pb = new B;
   pb->m_iNum = 100;
   D *pd1 = static_cast<D *>(pb);    //compile error???實踐好象沒有編譯錯誤
   D *pd2 = dynamic_cast<D *>(pb); //pd2 is NULL
   delete pb;
}

這里,可見,如果出現了交叉轉換的情況那么 dynamic_cast 將會返回空值。

在函數 foo 中,使用 static_cast 進行轉換是不被允許的,將在編譯時出錯(實踐中并沒有報錯);而使用 dynamic_cast 的轉換則是允許的,結果是空指針。 保險應當使用 dynamic_cast

reinterpret_cast

用法: reinterpret_cast<type-id> (expression)

功能

它可以把一個指針轉換成一個整數,也可以把一個整數轉換成一個指針(先把一個指針轉換成一個整數,再把該整數轉換成原類型的指針,還可以得到原先的指針值)。 type-id 必須是一個指針、引用、算術類型、函數指針或者成員指針。

描述

reinterpret_cast 是C++里的強制類型轉換符,支持任何轉換,但僅僅是如它的名字所描述的“重解釋”而已。也就是說:操作符修改了操作數類型,但僅僅是重新解釋了給出的對象的比特模型而沒有進行二進制轉換。

例如:

int *n= new int;
double *d=reinterpret_cast<double*> (n);

在進行計算以后, d 包含無用值.這是因為 reinterpret_cast 僅僅是復制 n 的比特位到 d, 沒有進行必要的分析。

reinterpret_cast 是為了映射到一個完全不同類型的意思,這個關鍵詞在我們需要把類型映射回原有類型時用到它。我們映射到的類型僅僅是為了故弄玄虛和其他目的,這是所有映射中最危險的(C++編程思想中的原話)。將 static_castreinterpret_cast 對比一下進行解釋,比較容易理解: static_cast 操作符修改了操作數類型,但是 reinterpret_cast 僅僅是重新解釋了給出的對象的比特模型而沒有進行二進制轉換。

例如:

int n=9;
double d=static_cast<double>(n);

上面的例子中, 我們將一個變量從 int 轉換到 double 。這些類型的二進制表達式是不同的,所以將整數9轉換到雙精度整數9, static_cast 需要正確地為雙精度整數 d 補足比特位。其結果為 9.0 。

reinterpret_cast 的行為卻不同:

int n=9;
double d=reinterpret_cast<double & >(n);

這里, 和 static_cast 不同,在進行計算以后, d 包含無用值。這是因為 reinterpret_cast 僅僅是復制 n 的比特位到 d, 沒有進行必要的分析。

因此, 需要謹慎使用 reinterpret_cast 。

舉例

這個例子,將 static_castreinterpret_cast 對比進行測試,具體的輸出參見其中的注釋。

 1 #include <iostream>
 2 using std::cout;
 3 using std::endl;
 4 class CBaseX
 5 {
 6     public:
 7         int x;
 8         CBaseX() { x = 10; }
 9         void foo() { printf("CBaseX::foo() x=%d/n", x); }
10 };
11 class CBaseY
12 {
13     public:
14         int y;
15         int* py;
16         CBaseY() { y = 20; py = &y; }
17         void bar() { printf("CBaseY::bar() y=%d, *py=%d/n", y, *py);}
18 };
19 class CDerived : public CBaseX, public CBaseY
20 {
21     public:
22         int z;
23 };
24
25 int main(int argc, char *argv[])
26 {
27     float f = 12.3;
28     float* pf = &f;
29
30     //基本類型的轉換
31     cout<<"=================Basic Cast================="<<endl;
32     //======static cast<>的使用:
33     int n = static_cast<int>(f); //成功編譯
34     cout<<"n is :"<<n<<endl;//n = 12
35     //int* pn = static_cast<int*>(pf);//編譯錯誤,指向的類型是無關的,不能將指針指向無關的類型
36     void* pv = static_cast<void*>(pf);//編譯成功
37     int* pn2 = static_cast<int*>(pv);//成功編譯, 但是 *pn2是無意義的內存(rubbish)
38     cout<<"pf is:"<<pf<<",pv is:"<<pv<<",pn2 is:"<<pn2<<endl;//三者值一樣
39     cout<<"*pf is:"<<*pf<<",*pn2 is:"<<*pn2<<endl;//pf=12.3,pn2是無用值,注意無法使用"*pv"因為編譯錯。
40
41     //======reinterpret_cast<>的使用:
42     //int i = reinterpret_cast<int>(f);//編譯錯誤,類型‘float’到類型‘int’的轉換無效.
43     //成功編譯, 但是 *pn 實際上是無意義的內存,和 *pn2一樣
44     int* pi = reinterpret_cast<int*>(pf);
45     cout<<"pf is:"<<pf<<",pi is:"<<pi<<endl;//值一樣
46     cout<<"*pf is:"<<*pf<<",*pi is:"<<*pi<<endl;//pi是無用值,和pn2一樣。
47
48
49     //對象類型的轉換
50     cout<<"=================Class Cast================="<<endl;
51     CBaseX cx;
52     CBaseY cy;
53     CDerived cd;
54
55     CDerived* pD = &cd;
56     CBaseX *pX = &cx;
57     CBaseY *pY = &cy;
58     cout<<"CDerived* pD = "<<pD<<endl;
59
60     //======static_cast<>的使用:
61     CBaseY* pY1 = pD;  //隱式static_cast轉換
62     //不一樣是因為多繼承,pD還要前移動以便也指向CBaseX.
63     cout<<"CDerived* pD = "<<pD<<",CBaseY* pY1 = "<<pY1<<endl;//pY1=pD+4!!!!!!
64
65     //CDerived* pD1 = pY1;//編譯錯誤,類型 ‘CBaseY*’ 到類型 ‘CDerived*’ 的轉換無效
66     CDerived* pD1 = static_cast<CDerived*>(pY1);//成功編譯
67     cout<<"CDerived* pD1 = "<<pD1<<endl;//現在 pD1 = pD
68
69     //pX = static_cast<CBaseX*>(pY);//編譯錯誤,從類型 ‘CBaseY*’ 到類型 ‘CBaseX*’ 中的 static_cast 無效。
70     pD1 = static_cast<CDerived*>(pY);//竟然可以編譯通過!!!!!!
71     cout<<"CDerived* pD1 = "<<pD1<<",CBaseY *pY = "<<pY<<endl;//現在 pD1 = pY-4
72     //======reinterpret_cast<>的使用:
73     CBaseY* pY2 = reinterpret_cast<CBaseY*>(pD);// 成功編譯, 但是 pY2 不是 CBaseY*
74     cout<<"CDerived* pD = "<<pD<<",CBaseY* pY2 = "<<pY2<<endl;//pY2=pD!!!!!!
75
76     //======通過void的轉換注意:
77     CBaseY* ppY = pD;
78     cout<<"CDerived* pD = "<<pD<<",CBaseY* ppY = "<<ppY<<endl;//ppY = pD + 4
79
80     void* ppV1 = ppY; //成功編譯
81     cout<<"CBaseY* ppY = "<<ppY<<",void* ppV1 = "<<ppV1<<endl;//ppV1 = ppY
82
83     //CDerived* ppD2 = ppV1;//編譯錯誤,類型‘void*’ 到類型 ‘CDerived*’的轉換無效
84     CDerived* ppD2 = static_cast<CDerived*>(ppV1);
85     cout<<"CDerived* ppD2 = "<<ppD2<<endl;//ppD2 = ppY, 但是我們預期 ppD2 = ppY - 4 = pD
86     //ppD2->bar();//系統崩潰,段錯誤
87     return 0;
88 }

這里,需要注意的地方是:

  • 第63行中基類指針 pY1 被賦予子類指針 pD 后, pY1=pD+4 而不是 pD ,因為 pD 是多繼承, pD 還要前移動以便也指向 CBaseX .

    內存布局大致如下:

    +CDerived------------------+ 
    |   +CBase X--------+      |\ 
    |   |  int x        |      | 4 bytes 
    |   +---------------+      |/ 
    |                          | 
    |   +CBase Y--------+      | 
    |   |  int y,*py    |      | 
    |   +---------------+      | 
    +--------------------------+
    
  • 第69行和70行的可以將父類指針用 static_cast 強制轉換成子類指針,但是兩個無關的類的指針之間卻不能轉換。

  • 第74行中使用 reinterpret_cast 將子類指針強制轉換賦給父類指針后,卻沒有像 static_cast 那樣將父類指針位置調整以指向正確的對象位置,這樣導致雖然兩者值是一樣的,但是父指針所指向的內容卻不是父對象了。

  • 第76行之后使用 void 將子類轉換成父類再轉回子類,卻無法使用了。

    因為任何指針可以被轉換到 void* ,而 void* 可以被向后轉換到任何指針(對于 static_cast<>reinterpret_cast<> 轉換都可以這樣做),如果沒有小心處理的話錯誤可能發生。一旦我們已經轉換指針為 void* ,我們就不能輕易將其轉換回原類(因為原類的信息在代碼中看不到了),所以使用 void 轉換的時候一定要小心。在上面的例子中,從一個 void* 返回 CDerived* 的唯一方法是將其轉換為 CBaseY* 然后再轉換為 CDerived* 。但是如果我們不能確定它是 CBaseY* 還是 CDerived* ,這時我們不得不用 dynamic_cast<>typeid[2] ( dynamic_cast<> 需要類成為多態,即包括“虛”函數,并因此而不能成為 void* )。

[其它]

dynamic_cast<> ,從另一方面來說,可以防止一個泛型 CBaseY* 被轉換到 CDerived* 。

const_cast

用法: const_cast<type_id> (expression)

功能

該運算符用來修改類型的 constvolatile 屬性。除了 constvolatile 修飾之外, type_idexpression 的類型是一樣的。

描述

const_cast 剝離一個對象的 const 屬性,允許對常量進行修改。

  • 常量指針被轉化成非常量指針,并且仍然指向原來的對象;
  • 常量引用被轉換成非常量引用,并且仍然指向原來的對象;
  • 常量對象被轉換成非常量對象。

Voiatileconst 類似。參見后面的例子可以了解更多信息。

舉例

給出的源代碼如下:

 1 #include <iostream>
 2 using std::cout;
 3 using std::endl;
 4
 5 class CTest
 6 {
 7     public:
 8         CTest(int i){m_val = i;cout<<"construction"<<m_val<<endl;}
 9         ~CTest(){cout<<"destructionn"<<endl;}
10         void SelfAdd(){m_val++;};
11         int m_val;
12 };
13
14 int main(int argc, char *argv[])
15 {
16     const int ic = 100;
17     //int cc = const_cast<int>(ic);//編譯錯誤
18     int cc = const_cast<int&>(ic);
19     cout<<cc<<endl;//輸出100
20     //const_cast<int &>(&ic)=200;//編譯錯誤,從類型 ‘const int*’ 到類型 ‘int&’ 中的 const_cast 無效
21     const_cast<int &>(ic)=200;
22     cout<<ic<<endl;//輸出100
23     cout<<*(&ic)<<endl;//輸出100
24     //int *pc = &ic;//編譯錯誤,從類型 ‘const int*’ 到類型 ‘int*’ 的轉換無效
25     const int *pc=&ic;
26     //const_cast<int &>(pc)=200;//編譯錯誤,從類型 ‘const int**’ 到類型 ‘int*’ 中的 const_cast 無效
27     const_cast<int &>(ic)=200;
28     //printf("%d,%d/n", ic, *pc);
29     cout<<ic<<','<<*pc<<endl;//100,200
30     //int *ppc = const_cast<int*>(ic);//編譯錯誤
31     int *ppc = const_cast<int*>(&ic);
32     *ppc = 300;
33     cout<<ic<<','<<*ppc<<endl;//100,300
34
35     const CTest test(1000);
36     CTest test2(1050);
37     //test = test2;//編譯錯誤,無法給常量賦值
38     const_cast<CTest &>(test)= test2;
39     cout<<test.m_val<<endl;//輸出1050
40 }

這里,結果輸出參見每行代碼相應的注釋。根據結果可知:

  • 凡是對結構體或類進行這個轉換,都是成功的。
  • char, short 等基本類型的轉換,通過直接打印變量顯示其值都是不成功的,但是通過指針卻能顯示出修改之后的值。

通過對代碼進行反匯編,可知,雖然本身我們沒使用優化,但系統還是對 ic 這個 const 進行了預編譯般的替換,將它替換成 64h (十六進制的64就是十進制的100),這肯定不是一般用戶想要的結果,如果它不是一個C++的規范,應該算是個C++的bug吧。

其他

注意下面一些問題。

操作對象

const_cast 操作的對象必須是 pointer, reference, nor a pointer-to-data-member type,如下代碼是錯誤的:

const int a = 5;
int aa = const_cast<int>(a);

而使用引用的方式,如下卻是正確的:

const int a = 5;
int aa = const_cast<int&>(a);

(2)可能的誤解

可能上面的描述誤解的地方,根據參考資料中的一個評論,說: const_cast 只能修改變量的常引用的 const 屬性,和變量的常指針的 const 屬性,還有對象的 const 屬性。要想改變常量本身的值是不可能的,也就是說,你改變的是引用的 const 屬性,而不是常量本身的 const 屬性。估計 const int ic = 100; 定義的時候就已經將這個基礎類型對象放入常量符號表里面了,永遠不會改變它的值。

五、其它

做為一個對前面所說的四種類型轉換操作符的補充,對它們之間的區別大致進行說明一下,如下。

static_castdynamic_cast 的對比

  • static_cast 在編譯期間發現錯誤。

    對于基本類型,它不允許將一種類型的指針指向另一種類型。所以如下代碼是錯誤的:

    float f = 12.3;
    float* pf = &f;
    int* pn = static_cast<int*>(pf);//編譯錯誤,指向的類型是無關的,不能將指針指向無關的類型
    

    對于復合類型(例如類、結構、聯合)它允許轉換子對象地址賦值給父指針,也允許轉換父對象地址賦值給子指針,但是不允許兩個無關的類之間的轉換,所以如下是錯誤的:

    CBaseX *pX = &cx;
    CBaseY *pY = &cy;
    pX = static_cast<CBaseX*>(pY);//編譯錯誤,從類型 ‘CBaseY*’ 到類型 ‘CBaseX*’ 中的 static_cast 無效。
    
  • dynamic_cast 在運行期間發生錯誤,它只允許它允許轉換子對象地址賦值給父指針,其它情況都返回空。

    例如:

    B *pb = new B;
    D *pd = dynamic_cast<D *>(pb); //pd is NULL
    delete pb;
    

static_cast, dynamic_cast, 和 reinterpret_cast 之間的對比

轉換的內容

static_castdynamic_cast 可轉換指針之間或實例之間

就是說,這兩個操作符可以執行指針到指針的轉換,或實例本身到實例本身的轉換,但不能在實例和指針之間轉換。

static_cast 只能提供編譯時的類型安全,而 dynamic_cast 可以提供運行時類型安全。

舉個例子:

class a;class b:a;class c;

上面個類 a 是基類, b 繼承 aca , b 沒有關系。假設有一個函數 void function(a&a); 現在有一個對象是 b 的實例 b ,一個 c 的實例 c 。 function(static_cast<a&>(b) 可以通過而 function(static<a&>(c)) 不能通過編譯,因為在編譯的時候編譯器已經知道 ca 的類型不符,因此 static_cast 可以保證安全。

reinterpret_cast 可任意轉換32bit的指針、實例

就是說,它可以轉換任意一個32bit整數,包括所有的指針和整數??梢园讶魏握麛缔D成指針,也可以把任何指針轉成整數,以及把指針轉化為任意類型的指針,威力最為強大!但不能將非32bit的實例轉成指針??傊?,只要是32bit的東東,怎么轉都行!

對于剛剛說的例子,下面我們騙一下編譯器,先把 c 轉成類型 a 。

b& ref_b = reinterpret_cast<b&>c;

這樣,

  • function(static_cast<a&>(ref_b)) 就通過了!因為從編譯器的角度來看,在編譯時并不能知道 ref_b 實際上是 c !
  • function(dynamic_cast<a&>(ref_b)) 編譯時能過,在運行時卻失敗了,因為 dynamic_cast 在運行時檢查了 ref_b 的實際類型,這樣怎么也騙不過去了。

檢測轉換的時機

在不同時機對轉換進行轉換,其安全性級別也不同,一般而言,這幾個運算符,若運行時檢測安全性最高,編譯時次之,而不檢測最危險。

  • 運行時:在應用多態編程時,當我們無法確定傳過來的對象的實際類型時使用 dynamic_cast
  • 編譯時:如果能保證對象的實際類型,用 static_cast 就可以了,
  • 不檢測:至于 reinterpret_cast 很象c語言那樣的暴力轉換。

總之, dynamic_cast 運行檢測, static_cast 編譯檢測, reinterpret_cast 不檢測

以上是從網上搜集的,以及根據自己的理解對C++中四種操作符號的總結,如有不準確的地方,感謝讀者的告知。另外文件的圖示內容由于格式轉換所以不太準確,可以存成文本格式的。_

參考資料

http://zhidao.baidu.com/question/81318972.html

http://blog.csdn.net/guogangj/article/details/1545119

http://zhidao.baidu.com/question/212970514.html

http://blog.csdn.net/deyili/article/details/5354242

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容