1. 程式人生 > >C++霧中風景11:釐清C++型別轉換(static_cast,dynamic_cast,reinterpret_cast,const_cast)

C++霧中風景11:釐清C++型別轉換(static_cast,dynamic_cast,reinterpret_cast,const_cast)

C++是一門弱型別的語言,提供了許多複雜和靈巧型別轉換的方式。筆者之前寫的Python與Go都是強型別的語言,對這種弱型別的設計實在是接受無力啊~~ ( 生活所迫,工作還得寫C++啊~~)C++語言提供了四種類型轉換的操作:static_cast,dynamic_cast,reinterpret_cast,const_cast,今天就來聊一聊,在C++之中應該如何來使用這些型別轉換的。

1.舊式型別轉換

開門見山,先聊聊筆者對型別轉換的看法吧。從設計上看,一門面向物件的語言是不一樣提供型別轉換的,這種方式破壞了型別系統。C++為了相容C也不得不吞下這個苦果,在實際進行程式設計工作的過程之中,並不太推薦大家使用型別轉換。(Java在這裡就做了一些妥協,在基本型別之中提供了型別轉換。對於物件型別則不提供型別轉換這種黑魔法

C++之中提供了兩種型別轉換的方式,第一種方式沿用了C語言之中的型別轉換,稱之為舊式型別轉換。說起來也很簡單,舉個栗子:

char x = 'c';
int y = (int) x;

這是最簡單的一箇舊式型別轉換,一個char型別被裝換為一個int型別。但是這種舊式的型別轉換是存在問題的:過於粗暴且極易失控,所以C++新提供了四種新式的型別轉換來替代舊式的型別轉換,這四種類型轉換分別用於不用的轉換場景,接下來筆者來一一梳理一下它們的用法。

2.新式的型別轉換

C++語言提供了四種新式型別轉換的操作: static_cast,dynamic_cast,reinterpret_cast,const_cast

,這些操作都依託了C++的模板來使用,標準的用法是

xxx_cast<轉換型別>(轉換引數)

這種新式轉換優於舊式的轉換就在於:編譯器可以在轉換期間進行更多的檢查,對於一些不符合轉換邏輯的轉換進行一些糾錯。而某些型別轉換操作可以利用RTTI(執行時型別資訊)來確保型別轉換的合理,這是舊式的型別轉換無法達成的效果。

  • const_cast

從名字上就可以看出來,這廝是用來對const屬性進行型別轉換的。這個名字取得有些偏頗,它同樣適用於volatile屬性。它可以為變數新增或接觸上述屬性,它也是新式轉換之中唯一具有這個能力的轉換方式,沒有什麼額外的坑,使用者體驗良好:(但是偶爾對於const屬性的轉換需要執行多步,先通過const_cast轉換,再借助其他轉換

//函式需要傳遞const屬性的變數,如atoi
atoi(const_cast<const char*>(char_ptr))
  • static_cast

static_cast 是靜態的轉換形式,不通過執行時型別檢查來保證轉換的安全性。它主要用於如下場合:

  • 用於基本資料型別之間的轉換,如把long轉換成char,把int轉換成char。

  • 用於面向物件程式設計之中基類與派生類之間的指標或引用轉換。它分為兩種 上行轉換(把派生類的指標或引用轉換成基類)是安全的; 下行轉換(把基類指標或引用轉換成派生類),由於沒有執行時的動態型別檢查,所以是不安全的。
  • 把非const屬性的型別轉換為const型別,但是不能將const型別轉換為非const型別,這個得藉助前文的const_cast。
  • void 的空指標轉換成其他型別的的空指標。

上面的幾種概念的比較好理解,這裡筆者著重聊聊上下行轉換:不囉嗦,看程式碼:

class Bird {
public:
    virtual void fly() {
        cout << "I can fly." << endl;
    }
};

class Penguin:public Bird {
public:
    void fly() {
        cout << "I can't fly." << endl;
    }
}; 

上述程式碼我們定義了兩個類BirdPenguin

int main() {
    Penguin* p = new (std::nothrow) Penguin;
    Bird* b = static_cast<Bird *>(p);

    b->fly();
    return 0;
}

上行轉換,將派生類轉換為基類的指標,合法。

int main() {
    Bird* b = new (std::nothrow) Bird;
    Penguin* p = static_cast<Penguin *>(b);
    
    if (p != nullptr) {
          p->fly();
     } else {
       
    }
    return 0;
}

下行轉換,將基類轉換為派生類的指標,此時程式的行為是不確定的。並且編譯期間並沒有警告,這是一種十分危險的用法,所以使用時一定要謹小慎微。所以接下來就要請出下一種轉換dynamic_cast,這是在物件基類和派生類之間轉換推薦的一種方式。

  • dynamic_cast

dynamic_cast主要用於在類層次間進行上下行轉換時,它與static_cast的最大的區別就在於dynamic_cast能夠在執行時進行型別檢查的功能,所以做起型別轉換比static_cast更安全,但是dynamic_cast會耗費更多的系統資源。dynamic_cast是無法通過舊式型別轉換完成的型別轉換。

int main() {
    Bird* b = new (std::nothrow) Bird;
    Penguin* p = dynamic_cast<Penguin *>(b);
    if (p != nullptr) {
        p->fly();
    } else {
        cout << "cast failed" << endl;
    }
    return 0;
}

dynamic_cast對於非法的下行轉換會返回空指標,所以可以在一定程度上避免不安全的型別轉換。

  • reinterpret_cast reinterpret_cast主要用於指標型別之間的轉換,和物件到指標型別之間的轉換。reinterpret就是對資料的位元位重新解釋轉換為我們需要轉換的物件。其與static_cast的區別在於其能處理有繼承關係類的指標和內建資料型別的轉換。而reinterpret_cast能夠處理所有指標(引用)之間的轉換
int main() {
    Bird* b = new (std::nothrow) Bird;
    Penguin* p = reinterpret_cast<Penguin *>(b);
    if (p != nullptr) {
        p->fly();
    } else {
        cout << "cast failed" << endl;
    }
    return 0;
}

上述程式碼依舊可以轉換成功,結果不可控

3.小結

梳理完C++新引進的四種類型轉換符之後,想必大家在實踐之中可以很好的運用好這些C++的型別轉換。後續筆者還會繼續深入的探討有關C++之中型別系統相關的內容,歡迎大家多多指教。