程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++類型轉換方式總結

C++類型轉換方式總結

編輯:C++入門知識

 

索引目錄

傳統轉換方式及用戶自定義轉換

const_cast

reinterpret_cast

static_cast

dynamic_cast

再談為何會有那四個轉換運算符

看起來,我應該把導師講過、遺漏的有關C++類型轉換方面的內容都總結成文了,主要內容都在以上幾篇文章中闡述完畢。

 

上邊的每一篇文章,雖然都單獨著重強調一種轉換方式或運算符,但是也有提到跟其他運算符之間的差異性,以及使用建議,因此基本可以看出各個運算符的使用方式和作用。

 

在文章也看到const_cast, reinterpret_cast, static_cast都可以用傳統的轉換方式基於指針進行替代。如果結合typeid運算符,那麼dynamic_cast其實也可以用傳統轉換方式實現。

 

因此不免會有疑惑:這四個轉換運算符不是很多余。

 

的確,我剛接觸這些轉換運算符的時候,我也又這樣的疑慮。因為在跟著導師學習C++,做一些課程項目的過程中,我都不曾使用過這些轉換運算符。一來我需要用到類型轉換的地方很少,二來也還不熟悉這些運算符,所以就盡量避免使用。

 

不過在花了這麼多時間和精力研究和總結這些轉換運算符之後,我以後一定會更多的去使用他們,因為傳統的轉換方式能夠實現標准轉換運算符的功能,主要還是基於"C++中的指針可以無條件互相轉換"。因此,對於轉換符的實現,其格式基本都是一致的:用強制轉換的方式,直接轉換指針類型。

 

正因如此,看到這些轉換代碼,有時候並不能馬上理解其目的用意。而如果用轉換運算符來操作,就可以一目了然地通過轉換符的名稱知道是在去除const,還是想進行指針的重新定義。

 

另一個角度來說,編譯器也對轉換運算符做來限制、優化和異常處理,使用他們可以更好地減少錯誤的產生,以及避免傳統轉換沒有達到預期的目的。

 

所以,如果碰到需要類型轉換的地方,就盡量思考,是否可以用轉換運算符來替代,用哪個是最合適的。下邊就來講講什麼時候用什麼樣的轉換符最合適。

 

轉換運算符的應用之所

結合網絡上各個站點看到的關於C++轉換符的知識,以及前面那些文章得到的反饋,可以將各個轉換運算符的使用總結如下:

 

對於傳統的轉換方式(C式或函數式),只在數值類型(包括整型、浮點型、字符類型和枚舉)上使用。這也是延續C的形式,當然這類轉換也是可以用static_cast來替換,但是因為是基本類型,所以傳統轉換已經很直觀。

 

對於const_cast轉換運算符,用在需要去除掉const限定的時候。其實這種情況出現的很少,可能的方法在const_cast一文中已經又舉例,不過還是反復強調, 使用const_cast轉換後,絕對不可試圖修改結果的值。

 

對於reinterpret_cast轉換運算符,一般用在將對象指針類型轉換到整數類型或者void * (空指針)。如同在文中舉出的隱患,因此注意的是,若要使用其結果,一定要將類型轉換回去後使用。也不要將隨意的整數轉換成指針類型。

 

對於static_cast轉換運算符,將其用在對象的轉換之上(雖然static_cast也可以用在有繼承關系的類型指針之間,但是還是將這方面的轉換交給dynamic_cast來操作吧),static_cast會調用相應的構造函數或者重載的轉換運算符。

 

通過文章的留言反饋,以及Google C++ Style Guide的推薦格式,知道對於單參構造函數的存在可能會引發一些隱式的轉換,因此用static_cast也可以明確的指出類型的轉換過程,避免生成多余的臨時對象造成效率下降。

 

對於dynamic_cast轉換運算符,將其用在具有繼承關系的指針類型之間的轉換。無論是從基類到子類的轉換,還是子類到基類的轉換,都將dynamic_cast套上去,也算是標識它們是一家子。

 

如果任何一種基於指針或引用的轉換,套上四個轉換運算符之後都失敗,那麼所要進行的轉換可能就觸到了"雷區"了:進行了沒意義的轉換。比如,對於沒有關系的兩個類型的指針進行了轉換,比如試圖轉換指向方法的指針了。所以轉換運算符對於避免代碼出錯也很有幫助。

 

基於引用(Reference)的轉換運算符使用

前面的文章中,所以對於轉換運算符的講述和舉例,都是基於指針的。但實際上,這些轉換運算符也可以基於引用來展開。准確說實際上引用類型應該是作為轉換的目標類型,源類型則是對象變量(當然也可能用的是引用變量,或是取出指針所指的內容,它們用到的都是實際的類對象)。

 

由於引用類型“定義時必須初始化”的特別,使得它不同於指針類型隨時隨地都調用轉換運算符,基於引用的轉換只在對引用進行初始化的時候才會出現。

 

下邊是const_cast和reinterpret_cast基於引用的運用:

 

 

    const int int_constant = 21;

    int& int_ref = const_cast<int&>(int_constant);

    cout << int_ref << endl;

   

    int int_value = 7;

    //long& long_ref = int_value; //Error, can not using reference cross types

    float& long_ref = reinterpret_cast<float&> (int_value);

    cout << long_ref << endl; 

    對於dynamic_cast的應用基本也是一致的,只是還是限制在具有繼承關系的類型之間。不同於基於指針在轉換時返回null,dynami_cast在基於引用轉換失敗時,會拋出std::bad_cast異常,因為不能將空值賦給引用類型。如果要抓住這個異常,則需要引入如下頭文件:

 

#include <typeinfo>

 

而static_cast轉換符前面已經說過推薦直接用在對象之上,不用在指針上,所以也不太會有需要用在引用類型上的情況出現。

 

山寨C#的TryParse

C#中有很多簡潔實用的轉換方法,比如從字符串到數值類型的Parse和TryParse,還有包含了各種從object對象到數值類型、時間類型的方法的Convert類,以及檢查繼承關系的as運算符。

 

從返回的結果看,C++和dynamic_cast和C#的as很相似,兩者都是在失敗時候返回null。

 

不過面向對象的關鍵點在於什麼都是以對象為操作單位,如前所講dynamic_cast看起來更像是一個全局方法。因此我便模仿C#的數值類的TryParse方法,寫了一個包裹dynamic_cast的類型轉換方法:

 

 

/////////////////////////////////////////////////////////////////////////////

// dynamic_cast_tryparse.cpp                                                     

// Language:    C++                  

// Complier:    Visual Studio 2010, Xcode3.2.6

// Platform:    MacBook Pro 2010

// Application: none 

// Author:      Ider, Syracuse University, [email protected]

///////////////////////////////////////////////////////////////////////////

#include <string>

#include <iostream>

using namespace std;

 

class Parents

{

public:

    Parents(string n="Parent"){ name = n;}

    virtual ~Parents(){}

   

    virtual void Speak()

    {

        cout << "\tI am " << name << ", I love my children." << endl;

    }

    void Work()

    {

        cout << "\tI am " << name <<", I need to work for my family." << endl;;

    }

   

    /************** TryParseTo **************/

    template<typename T> bool TryParseTo(T** outValue)

    {

        T* temp = dynamic_cast<T*> (this);

        if (temp == NULL) return false;

       

        *outValue = temp;

        return true;

    }

   

protected:

    string name;

};

 

class Children : public Parents

{

public:

    Children(string n="Child"):Parents(n){ }

   

    virtual ~Children(){}

   

    virtual void Speak()

    {

        cout << "\tI am " << name << ", I love my parents." << endl;

    }

    /*

     **Children inherit Work() method from parents,

     **it could be treated like part-time job.

     */

    void Study()

    {

        cout << "\tI am " << name << ", I need to study for future." << endl;;

    }

   

private:

    //string name; //Inherit "name" member from Parents

};

 

class Stranger

{

public:

    Stranger(string n="stranger"){name = n;}

    virtual ~Stranger(){}

   

    void Self_Introduce()

    {

        cout << "\tI am a stranger" << endl;

    }

    void Speak()

    {

        //cout << "I am a stranger" << endl;

        cout << "\tDo not talk to "<< name << ", who is a stranger." << endl;

    }

private:

    string name;

};

 

int main()

{

 

    Children * parsedChild;

    Parents * parsedParent;

    Stranger * parsedStranger;

   

    Parents * mother = new Parents("Mother who pretend to be a my daugher");

    if(mother->TryParseTo<Children>(&parsedChild))

        parsedChild->Speak();

    else

        cout << "Parents parse to Children failed" << endl;

   

    delete mother;

   

    mother = new Children("Daughter who pretend to be a my mother");

    if(mother->TryParseTo<Children>(&parsedChild))

        parsedChild->Speak();

    else

        cout << "Parents parse to Children failed" << endl;

   

    delete mother;

   

    Children * son = new Children("Son who pretend to be a my father");

    if(son->TryParseTo<Parents>(&parsedParent))

        parsedParent->Speak();

    else

        cout << "Children parse to Parents failed" << endl;

   

    if(son->TryParseTo<Stranger>(&parsedStranger))

        parsedStranger->Speak();

    else

        cout << "Children parse to Stranger failed" << endl;

   

    delete son;

   

    //pointer of child class could pointer to base class object

   

    /*

    * son = new Parents("Father who pretend to be a my son");

    if(son->TryParseTo<Parents>(&parsedParent))

        parsedParent->Speak();

    else

        cout << "Parse failed" << endl;

   

    delete son;

    */

 

    return 0;

}

 

/********************* Result *********************/

 

//Parents parse to Children failed

//    I am Daughter who pretend to be a my mother, I love my parents.

//    I am Son who pretend to be a my father, I love my parents.

//Children parse to Stranger failed

 

  這段代碼中使用到的類跟dynamic_cast一文中使用的基本一樣,只是在基類中多了一個模板方法:

 

 

    template<typename T> bool TryParseTo(T** outValue)

    {

        T* temp = dynamic_cast<T*> (this);

        if (temp == NULL) return false;

       

        *outValue = temp;

        return true;

    }

    該方法需要指定的目標類型,將自身指針轉換成目標指針。轉換成功,則將結果賦值給相應的變量,並返回真值;若失敗則返回假值,不改變指針變量。因為要讓外部的指針變量能夠接受到改值,因此不得不使用指向指針的指針。

 

因為在基類中以公共結果的形式出現,所以每一個子類都繼承了該方法,無論是基類的對象還是子類的對象都可以調用該方法。而該方法又不是虛方法,因此不並不希望子類去修改它。只是因為方法是模板方法,可能在編譯的時候需要多花一些時間。

 

由於引用必須在定義時就賦值,並且dynamic_cast對於基於引用的轉換不成功時將拋出異常,因此對於基於引用的轉換,我還沒有想出有什麼好的山寨形式。

 

從測試代碼的結果也可以看出,對於該發放的調用都是成功有效的。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved