歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

C++ 新標准下的強制類型轉換詳解

使用標准C++的類型轉換符:static_castdynamic_castreinterpret_castconst_cast

static_cast

用法:static_cast<type_id> (expression)

該運算符把expression轉換為type-id類型,但沒有運行時類型檢查來保證轉換的安全性。它主要有如下幾種用法:

  • 用於類層次結構中基類和派生類之間指針或引用的轉換 進行上行轉換(把派生類的指針或引用轉換成基類表示)是安全的進行下行轉換(把基類的指針或引用轉換為派生類表示),由於沒有動態類型檢查,所以是不安全的
  • 用於基本數據類型之間的轉換,如把int轉換成char。這種轉換的安全也要開發人員來保證
  • 把空指針轉換成目標類型的空指針
  • 把任何類型的表達式轉換為void類型

注意:static_cast不能轉換掉expressionconstvolitale或者__unaligned屬性。

dynamic_cast

用法:dynamic_cast<type_id> (expression)

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

有條件轉換,動態類型轉換,運行時類型安全檢查(轉換失敗返回NULL):

  1. 安全的基類和子類之間轉換。

  2. 必須要有虛函數。

  3. 相同基類不同子類之間的交叉轉換。但結果是NULL

如果type_id是類指針類型,那麼expression也必須是一個指針,如果type_id是一個引用,那麼expression也必須是一個引用。

dynamic_cast主要用於類層次間的上行轉換和下行轉換,還可以用於類之間的交叉轉換。

在類層次間進行上行轉換時,dynamic_caststatic_cast的效果是一樣的;
在進行下行轉換時,dynamic_cast具有類型檢查的功能,比static_cast更安全。

dynamic_cast只用於對象的指針和引用。當用於多態類型時,它允許任意的隱式類型轉換以及相反過程。不過,與static_cast不同,在後一種情況裡(注:即隱式轉換的相反過程),dynamic_cast會檢查操作是否有效。也就是說,它會檢查轉換是否會返回一個被請求的有效的完整對象。

檢測在運行時進行。如果被轉換的指針不是一個被請求的有效完整的對象指針,返回值為NULL.

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);
}

在上面的代碼段中,如果pb指向一個D類型的對象,pd1pd2是一樣的,並且對這兩個指針執行D類型的任何操作都是安全的;

但是,如果pb指向的是一個B類型的對象,那麼pd1將是一個指向該對象的指針,對它進行D類型的操作將是不安全的(如訪問m_szName),而pd2將是一個空指針。

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

另外,dynamic_cast還支持交叉轉換,如下所示。

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;    
}

在函數foo中,使用static_cast進行轉換是不被允許的,將在編譯時出錯,而使用dynamic_cast轉換則是允許的,結果是空指針。

reinterpret_cast

用法:reinterpret_cast<type_id> (expression)

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

這個操作符能夠在非相關的類型之間轉換。操作結果只是簡單的從一個指針到別的指針的值的二進制拷貝。在類型之間指向的內容不做任何類型的檢查和轉換。如果情況是從一個指針到整型的拷貝,內容的解釋是系統相關的,所以任何的實現都不是方便的。一個轉換到足夠大的整型能夠包含它的指針是能夠轉換回有效的指針的。

該運算符的用法比較多。
(static_cast .. reinterpret_cast比較,見下面 )
該運算符平台移植性比較差。

const_cast

用法:const_cast<type_id> (expression)

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

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

它允許子類類型的指針轉換為父類類型的指針(這是一個有效的隱式轉換),同時,也能夠執行相反動作:轉換父類為它的子類。

這個轉換類型操縱傳遞對象的const屬性,或者是設置或者是移除

volatileconst類型,舉例如下所示。

class B
{
public:    
    int m_iNum;    
}

void foo()
{
    const B b1;    
    b1.m_iNum = 100; //comile error    
    B b2 = const_cast<B>(b1);    
    b2. m_iNum = 200; //fine    
}

上面的代碼編譯時會報錯,因為b1是一個常量對象,不能對它進行改變;
使用const_cast把它轉換成一個非常量對象,就可以對它的數據成員任意改變。注意:b1b2是兩個不同的對象。

比較

dynamic_cast vs static_cast

class B 
{ 
    ... 
};

class D : public B 
{ 
    ...
};


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

dynamic_cast可用於繼承體系中的向下轉型,即將基類指針轉換為派生類指針,比static_cast更嚴格更安全。dynamic_cast在執行效率上比static_cast要差一些,但static_cast在更寬上范圍內可以完成映射,這種不加限制的映射伴隨著不安全性。static_cast覆蓋的變換類型除類層次的靜態導航以外,還包括無映射變換、窄化變換(這種變換會導致對象切片,丟失信息)、用VOID*的強制變換、隱式類型變換等…

static_cast vs reinterpret_cast

reinterpret_cast是為了映射到一個完全不同類型的意思,這個關鍵詞在我們需要把類型映射回原有類型時用到它。我們映射到的類型僅僅是為了故弄玄虛和其他目的,這是所有映射中最危險的。(這句話是C++編程思想中的原話)

static_cast 和 reinterpret_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);

這次, 結果有所不同. 在進行計算以後, d 包含無用值. 這是因為 reinterpret_cast 僅僅是復制 n 的比特位到 d, 沒有進行必要的分析.
因此, 你需要謹慎使用 reinterpret_cast.

補充:

(1)static_cast:在功能上基本上與C風格的類型轉換一樣強大,含義也一樣。它有功能上的限制。例如,你不能用static_cast像用C風格轉換一樣把struct轉換成int類型或者把double類型轉換成指針類型。另外,static_cast不能從表達式中去除const屬性,因為另一個新的類型轉換符const_cast有這樣的功能。

可以靜態決議出類型的轉換可能性,即使是在繼承體系中,即使包括了多重繼承和虛繼承,只要可以進行靜態決議就可以轉換成功

(2)const_cast:用於類型轉換掉表達式的constvolatile屬性。通過使用const_cast,你向人們和編譯器強調你通過類型轉換想做的只是改變一些東西的constness或者volatieness屬性。這個含義被編譯器所約束。如果你試圖使用const_cast來完成修改constness或者volatileness屬性之外的事情,你的類型轉換將被拒絕。

(3)dynamic_cast:它被用於安全地沿著類的繼承關系向下進行類型轉換。這就是說,你能用dynamic_cast把指向基類的指針或引用轉換成指向其派生類或其兄弟類的指針或引用,而且你能知道轉換是否成功。失敗的轉換將返回空指針(當對指針進行類型轉換時)或者拋出異常(當對引用進行類型轉換時)。

(4)reinterpret_cast:使用這個操作符的類型轉換,其轉換結果幾乎都是執行期定義。因此,使用reinterpret_cast的代碼很難移植。reinterpret_casts的最普通的用途就是在函數指針類型之間進行轉換


## C++的四種強制轉型形式每一種適用於特定的目的:

  • dynamic_cast 主要用於執行“安全的向下轉型(safe downcasting)”,也就是說,要確定一個對象是否是一個繼承體系中的一個特定類型。它是唯一不能用舊風格語法執行的強制轉型,也是唯一可能有重大運行時代價的強制轉型。

  • static_cast 可以被用於強制隱型轉換(例如,non-const 對象轉型為 const 對象,int 轉型為 double,等等),它還可以用於很多這樣的轉換的反向轉換(例如,void* 指針轉型為有類型指針,基類指針轉型為派生類指針),但是它不能將一個 const 對象轉型為 non-const 對象(只有 const_cast 能做到),它最接近於C-style的轉換。

  • const_cast 一般用於強制消除對象的常量性。它是唯一能做到這一點的 C++ 風格的強制轉型。

  • reinterpret_cast是特意用於底層的強制轉型,導致實現依賴(implementation-dependent)(就是說,不可移植)的結果,例如,將一個指針轉型為一個整數。這樣的強制轉型在底層代碼以外應該極為罕見。 

------------------------------分割線------------------------------

C++ Primer Plus 第6版 中文版 清晰有書簽PDF+源代碼 http://www.linuxidc.com/Linux/2014-05/101227.htm

讀C++ Primer 之構造函數陷阱 http://www.linuxidc.com/Linux/2011-08/40176.htm

讀C++ Primer 之智能指針 http://www.linuxidc.com/Linux/2011-08/40177.htm

讀C++ Primer 之句柄類 http://www.linuxidc.com/Linux/2011-08/40175.htm

將C語言梳理一下,分布在以下10個章節中:

  1. Linux-C成長之路(一):Linux下C編程概要 http://www.linuxidc.com/Linux/2014-05/101242.htm
  2. Linux-C成長之路(二):基本數據類型 http://www.linuxidc.com/Linux/2014-05/101242p2.htm
  3. Linux-C成長之路(三):基本IO函數操作 http://www.linuxidc.com/Linux/2014-05/101242p3.htm
  4. Linux-C成長之路(四):運算符 http://www.linuxidc.com/Linux/2014-05/101242p4.htm
  5. Linux-C成長之路(五):控制流 http://www.linuxidc.com/Linux/2014-05/101242p5.htm
  6. Linux-C成長之路(六):函數要義 http://www.linuxidc.com/Linux/2014-05/101242p6.htm
  7. Linux-C成長之路(七):數組與指針 http://www.linuxidc.com/Linux/2014-05/101242p7.htm
  8. Linux-C成長之路(八):存儲類,動態內存 http://www.linuxidc.com/Linux/2014-05/101242p8.htm
  9. Linux-C成長之路(九):復合數據類型 http://www.linuxidc.com/Linux/2014-05/101242p9.htm
  10. Linux-C成長之路(十):其他高級議題

Copyright © Linux教程網 All Rights Reserved