C++ 強制類型轉換詳解

一、C強制轉換

C語言中的強制轉換主要用於普通數據類型、指針的強制轉換,沒有類型檢查,轉換不安全,

語法為:

(type-id)expression//轉換格式1
type-id(expression)//轉換格式2(基本已經不用瞭)

二、C++強制轉換

C++除瞭能使用c語言的強制類型轉換外,還新增瞭四種強制類型轉換:static_castdynamic_castconst_castreinterpret_cast

主要運用於繼承關系類間的強制轉化,語法為:

//靜態轉換
static_cast<new_type>      (expression)
//動態轉換
dynamic_cast<new_type>     (expression) 
//常量轉換
const_cast<new_type>       (expression) 
//重新解釋轉換
reinterpret_cast<new_type> (expression)

其中new type為轉換後的新類型,expression為舊類型

1、static_cast 靜態轉換(編譯時檢查)

用法:  static_cast <類型說明符> (變量或表達式)

static_cast靜態轉換相當於C語言中的強制轉換,但不能實現普通指針數據(空指針除外)的強制轉換,一般用於父類和子類指針、引用間的相互轉換。

用於類層次結構中基類(父類)和派生類(子類)之間 指針 或 引用 的轉換。不管是否發生多態,父子之間互轉時,編譯器都不會報錯。

  • (1)進行 上行轉換 (把派生類的指針或引用轉換成基類表示)是 安全 的;
  • (2)進行 下行轉換 (把基類指針或引用轉換成派生類表示)時,由於沒有動態類型檢查,所以是 不安全 的,但是編譯器不會報錯。

用於基本數據類型之間的轉換,如把int轉換成char,把int轉換成enum。這種轉換的安全性也要開發人員來保證。

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

把任何指針類型轉換成空指針類型。

註意:static_cast不能轉換掉expression的const、volatile、或者__unaligned屬性

如果涉及到類的話,static_cast隻能在有相互聯系的類型中進行相互轉換,不一定包含虛函數。

在C++語言中static_cast用於數據類型的強制轉換,強制將一種數據類型轉換為另一種數據類型。例如將整型數據轉換為浮點型數據。

[例1]C語言所采用的類型轉換方式:

#include <iostream>
using namespace std;

int main() {
 int a = 10;
 int b = 3;
 double result = (double)a / (double)b;

 cout << result << endl; // 3.33333

 return 0;
}

例1中將整型變量a和b轉換為雙精度浮點型,然後相除。在C++語言中,我們可以采用static_cast關鍵字來進行強制類型轉換,如下所示。

[例2]static_cast關鍵字的使用:

#include <iostream>
using namespace std;

int main() {
 int a = 10;
 int b = 3;
 double result = static_cast<double> (a) / static_cast<double> (b);
 //其實寫一個 static_cast<double> 就行
 
 cout << result << endl; // 3.33333

 return 0;
}

在本例中同樣是將整型變量a轉換為雙精度浮點型。采用static_cast進行強制數據類型轉換時,將想要轉換成的數據類型放到尖括號中,將待轉換的變量或表達式放在元括號中。

2、const_cast 常量轉換

在C語言中,const限定符通常被用來限定變量,用於表示該變量的值不能被修改。

上邊的 static_cast 不能將 const int* 轉成 int* const_cast 就可以,

用法:  const_cast<type-i> (expression)

const_cast ,用於修改類型的constvolatile屬性,隻能對是 引用 或者 指針 的變量添加或移除const。(除瞭const volatile修飾之外, type_idexpression的類型是一樣的。)

const_cast則正是用於強制去掉這種不能被修改的常數特性,但需要特別註意的是const_cast不是用於去除變量的常量性,而是去除 指向常數對象的指針或引用 的常量性,其去除常量性的對象必須為指針或引用。

  • 常量指針被轉化成非常量指針,並且仍然指向原來的對象;
  • 常量引用被轉換成非常量引用,並且仍然指向原來的對象;常量對象被轉換成非常量對象。
int main()
{
    const int a = 10;
    const int* p = &a;
    int* q = const_cast<int*>(p);
    *q = 20;    //fine

    cout << "a=" << a << " " << "&a = " << &a << endl;
    cout << "*p=" << *p << " " << "p = " << p << endl;
    cout << "*q=" << *q << " " << "q = " << q << endl;

    return 0;
}

//a = 10 & a = 012FFC10
//* p = 20 p = 012FFC10
//* q = 20 q = 012FFC10

int main() {
    int c = 11;
    const int a = c;
    const int* p = &a;
    int* q = const_cast<int*>(p);
    *q = 20;    //fine

    cout << "a=" << a << " " << "&a = " << &a << endl;
    cout << "*p=" << *p << " " << "p = " << p << endl;
    cout << "*q=" << *q << " " << "q = " << q << endl;

    return 0;
}

//a = 20 &a = 007BFD64
//* p = 20 p = 007BFD64
//* q = 20 q = 007BFD64

int main() {
    const int c = 11;
    const int a = c;
    const int* p = &a;
    int* q = const_cast<int*>(p);
    *q = 20;    //fine

    cout << "a=" << a << " " << "&a = " << &a << endl;
    cout << "*p=" << *p << " " << "p = " << p << endl;
    cout << "*q=" << *q << " " << "q = " << q << endl;

    return 0;
}

//a = 11 & a = 00EFFB44
//* p = 20 p = 00EFFB44
//* q = 20 q = 00EFFB44

查看運行結果,問題來瞭,指針p和指針q都是指向a變量的,指向地址相同,而且經過調試發現012FFC10地址內的值確實由10被修改成瞭20,這是怎麼一回事呢?為什麼a的值打印出來還是10呢?

其實這是一件好事,我們要慶幸a變量最終的值沒有變成20!變量a一開始就被聲明為一個常量變量,不管後面的程序怎麼處理,它就是一個常量,就是不會變化的。試想一下如果這個變量a最終變成瞭20會有什麼後果呢?對於這些簡短的程序而言,如果最後a變成瞭20,我們會一眼看出是q指針修改瞭,但是一旦一個項目工程非常龐大的時候,在程序某個地方出現瞭一個q這樣的指針,它可以修改常量a,這是一件很可怕的事情的,可以說是一個程序的漏洞,畢竟將變量a聲明為常量就是不希望修改它,如果後面能修改,這就太恐怖瞭。

我們稱“*q=20”語句為未定義行為語句,所謂的未定義行為是指在標準的C++規范中並沒有明確規定這種語句的具體行為,該語句的具體行為由編譯器來自行決定如何處理。對於這種未定義行為的語句我們應該盡量予以避免!

3、reinterpret_cast 重新解釋轉換

在C++語言中,reinterpret_cast 主要有三種強制轉換用途:

  • 改變指針或引用的類型
  • 將指針或引用轉換為一個足夠長度的整形
  • 將整型轉換為指針或引用類型

用法:  reinterpret_cast<type_id> (expression)

type-id必須是一個指針、引用、算術類型、函數指針或者成員指針。

它可以把一個指針轉換成一個整數,也可以把一個整數轉換成一個指針(先把一個指針轉換成一個整數,在把該整數轉換成原類型的指針,還可以得到原先的指針值)。

我們映射到的類型僅僅是為瞭故弄玄虛和其他目的,這是所有映射中最危險的。(這句話是C++編程思想中的原話)。因此, 你需要謹慎使用 reinterpret_cast

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

在上面代碼中,將整型指針通過reinterpret_cast強制轉換成瞭雙精度浮點型指針。
reinterpret_cast可以將指針或引用轉換為一個足夠長度的整形,此中的足夠長度具體長度需要多少則取決於操作系統,如果是32位的操作系統,就需要4個字節及以上的整型,如果是64位的操作系統則需要8個字節及以上的整型。

typedef void (* FUNC)();
int DoSomething (int i)
{
    cout<< "DoSomething" <<endl;
 return 0; 
}

void Test () {
 // reinterpret_cast可以編譯器以FUNC的定義方式去看待DoSomething函數
 // 所以非常的BUG,下面轉換函數指針的代碼是不可移植的,所以不建議這樣用
 // C++不保證所有的函數指針都被一樣的使用,所以這樣用有時會產生不確定的結果 
 FUNC f = reinterpret_cast<FUNC>(DoSomething);
 f();
}

4、dynamic_cast 動態轉換(運行時檢查)

主要用於類層次結構中基類(父類)和派生類(子類)之間指針或引用的轉換(隻能用於類間轉換,支持類間交叉轉換,不能操作普通數據。)

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

  • 向上轉換,即為子類指針指向父類指針(一般不會出問題);向下轉換,即將父類指針轉化子類指針。
  • 向下轉換的成功與否還與將要轉換的類型有關,即要轉換的指針指向的對象的實際類型與轉換以後的對象類型一定要相同,否則轉換失敗。
  • 在C++中,編譯期的類型轉換有可能會在運行時出現錯誤,特別是涉及到類對象的指針或引用操作時,更容易產生錯誤。Dynamic_cast操作符則可以在運行期對可能產生問題的類型轉換進行測試。

(2)發生多態時,允許互相轉換。
(3)無繼承關系的類之間也可以相互轉換,類之間的交叉轉換。
(4)如果dynamic_cast語句的轉換目標是指針類型並且失敗瞭,則結果為0。如果轉換目標是引用類型並且失敗瞭,則dynamic_cast運算符將拋出一個std::bad_cast異常
(5)使用 dynamic_cast 進行轉換的,基類中一定要有虛函數,否則編譯不通過(類中存在虛函數,就說明它有想要讓基類指針或引用指向派生類對象的情況,此時轉換才有意義)。這是由於運行時類型檢查需要運行時類型信息,而這個信息存儲在類的虛函數表中,隻有定義瞭虛函數的類才有虛函數表。

class base {
public:
    void print1() { cout << "in class base" << endl; }
};

class derived : public base {
public:
    void print2() { cout << "in class derived" << endl; }
};

int main() {
    derived* p, * q;
    // p = new base; //  Compilr Error: 無法從 "base * " 轉換為 "derived * "

    // Compile Error: Cannot cast from 'base*' to 'derived*' via dynamic_cast: expression type is not polymorphic(多態的)
    // p = dynamic_cast<derived *>(new base);

    q = static_cast<derived*>(new base); // ok, but not recommended(推薦)

    q->print1(); // in class base
    q->print2(); // in class derived
    
    return 0;
}

從上邊的代碼可以看出用一個派生類的指針是不能直接指向一個基類的對象的,會出現編譯錯誤。用 dynamic_cast 的話也會編譯錯誤,提示我們基類不是多態的,也就是基類中沒有虛函數。可以看到 static_cast 是可以編譯通過的,且輸出結果看起來都是對的

static_cast 強制類型轉換時並不具有保證類型安全的功能,而 C++ 提供的 dynamic_cast 卻能解決這一問題,dynamic_cast 可以在程序運行時檢測類型轉換是否類型安全。當然 dynamic_cast 使用起來也是有條件的,它要求所轉換的 expression 必須包含多態類類型(即至少包含一個虛函數的類)。

三、要點總結

  • static_cast:在功能上基本上與C風格的類型轉換一樣強大,含義也一樣。它有功能上的限制。例如,你不能用static_cast像用C風格轉換一樣把struct轉換成int類型或者把double類型轉換成指針類型。另外,static_cast不能從表達式中去除const屬性,因為另一個新的類型轉換符const_cast有這樣的功能。可以靜態決議出類型的轉換可能性,即使是在繼承體系中,即使包括瞭多重繼承和虛繼承,隻要可以進行靜態決議就可以轉換成功
  • const_cast:用於類型轉換掉表達式的constvolatile屬性。通過使用const_cast,你向人們和編譯器強調你通過類型轉換想做的隻是改變一些東西的constness或者volatieness屬性。這個含義被編譯器所約束。如果你試圖使用const_cast來完成修改constness或者volatileness屬性之外的事情,你的類型轉換將被拒絕。
  • reinterpret_cast:使用這個操作符的類型轉換,其轉換結果幾乎都是執行期定義。因此,使用reinterpret_cast的代碼很難移植。reinterpret_casts的最普通的用途就是在函數指針類型之間進行轉換。
  • dynamic_cast:它被用於安全地沿著類的繼承關系向下進行類型轉換。這就是說,你能用dynamic_cast把指向基類的指針或引用轉換成指向其派生類或其兄弟類的指針或引用,而且你能知道轉換是否成功。失敗的轉換將返回空指針(當對指針進行類型轉換時)或者拋出異常(當對引用進行類型轉換時)。

RTTI(Run-time Type identification)  :

通過運行時類型信息程序能夠使用 基類的指針或引用 來檢查這些指針或引用所指的對象的實際派生類型。(運行時類型識別)

(1)typeid操作符,返回指針和引用所指的實際類型:

可以判斷變量的類型,可以判斷兩個變量的類型是否相同,可以打印變量的類型(name())

比如:
typeid(a).name()就能知道變量a是什麼類型

(2)dynamic_cast操作符,將基類類型的指針或引用安全地轉換為派生類型的指針或引用:

利用RTTI技術進行識別的父子類指針間轉化。會阻止原生的父類指針轉換為子類指針。阻止的方式是扔出一個bad_cast異常,且表達式的值變為NULL

到此這篇關於C/C++ 強制類型轉換詳解的文章就介紹到這瞭,更多相關C/C++ 強制類型轉換內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: