C++<第三十三篇>:四種類型轉(zhuǎn)換

在 C++ 中,不同數(shù)據(jù)類型之間可以相互轉(zhuǎn)換。無需用戶指明如何轉(zhuǎn)換的稱為自動(dòng)類型轉(zhuǎn)換(隱式類型轉(zhuǎn)換),需要用戶顯式的指明如何轉(zhuǎn)換的稱為強(qiáng)制類型轉(zhuǎn)換。
隱式類型轉(zhuǎn)換是安全的,顯式類型轉(zhuǎn)換是有風(fēng)險(xiǎn)的,C語言之所以增加強(qiáng)制類型轉(zhuǎn)換的語法,就是為了強(qiáng)調(diào)風(fēng)險(xiǎn),讓程序員意識(shí)到自己在做什么。
但是,這種強(qiáng)調(diào)風(fēng)險(xiǎn)的方式還是比較粗放,粒度比較大,它并沒有表明存在什么風(fēng)險(xiǎn),風(fēng)險(xiǎn)程度如何。再者,C風(fēng)格的強(qiáng)制類型轉(zhuǎn)換統(tǒng)一使用 ( )。
為了使?jié)撛陲L(fēng)險(xiǎn)更加細(xì)化,使問題追溯更加方便,使書寫格式更加規(guī)范,C++ 對(duì)類型轉(zhuǎn)換進(jìn)行了分類,并新增了四個(gè)關(guān)鍵字來予以支持,它們分別是:static_cast、const_cast、reinterpret_cast、dynamic_cast。

(1)自動(dòng)類型轉(zhuǎn)換 和 自動(dòng)類型轉(zhuǎn)換

自動(dòng)類型轉(zhuǎn)換示例:

int a = 6;
a = 7.5 + a;

編譯器對(duì) 7.5 是作為 double 類型處理的,在求解表達(dá)式時(shí),先將 a 轉(zhuǎn)換為 double 類型,然后與 7.5 相加,得到和為 13.5。在向整型變量 a 賦值時(shí),將 13.5 轉(zhuǎn)換為整數(shù) 13,然后賦給 a。整個(gè)過程中,我們并沒有告訴編譯器如何去做,編譯器使用內(nèi)置的規(guī)則完成數(shù)據(jù)類型的轉(zhuǎn)換。

強(qiáng)制類型轉(zhuǎn)換示例:

int n = 100;
int *p1 = &n;
float *p2 = (float*)p1;

p1 是int *類型,它指向的內(nèi)存里面保存的是整數(shù),p2 是float *類型,將 p1 賦值給 p2 后,p2 也指向了這塊內(nèi)存,并把這塊內(nèi)存中的數(shù)據(jù)作為小數(shù)處理。我們知道,整數(shù)和小數(shù)的存儲(chǔ)格式大相徑庭,將整數(shù)作為小數(shù)處理非?;恼Q,可能會(huì)引發(fā)莫名其妙的錯(cuò)誤,所以編譯器默認(rèn)不允許將 p1 賦值給 p2。但是,使用強(qiáng)制類型轉(zhuǎn)換后,編譯器就認(rèn)為我們知道這種風(fēng)險(xiǎn)的存在,并進(jìn)行了適當(dāng)?shù)臋?quán)衡,所以最終還是允許了這種行為。

不管是自動(dòng)類型轉(zhuǎn)換還是強(qiáng)制類型轉(zhuǎn)換,前提必須是編譯器知道如何轉(zhuǎn)換,例如,將小數(shù)轉(zhuǎn)換為整數(shù)會(huì)抹掉小數(shù)點(diǎn)后面的數(shù)字,將int *轉(zhuǎn)換為float *只是簡(jiǎn)單地復(fù)制指針的值,這些規(guī)則都是編譯器內(nèi)置的,我們并沒有告訴編譯器。

(2)4種類型轉(zhuǎn)換簡(jiǎn)單說明

為了使?jié)撛陲L(fēng)險(xiǎn)更加細(xì)化,使問題追溯更加方便,使書寫格式更加規(guī)范,C++ 對(duì)類型轉(zhuǎn)換進(jìn)行了分類,并新增了四個(gè)關(guān)鍵字來予以支持,它們分別是:

關(guān)鍵字 說明
static_cast 用于良性轉(zhuǎn)換,一般不會(huì)導(dǎo)致意外發(fā)生,風(fēng)險(xiǎn)很低。
const_cast 用于 const 與非 const、volatile 與非 volatile 之間的轉(zhuǎn)換。
reinterpret_cast 高度危險(xiǎn)的轉(zhuǎn)換,這種轉(zhuǎn)換僅僅是對(duì)二進(jìn)制位的重新解釋,不會(huì)借助已有的轉(zhuǎn)換規(guī)則對(duì)數(shù)據(jù)進(jìn)行調(diào)整,但是可以實(shí)現(xiàn)最靈活的 C++ 類型轉(zhuǎn)換。
dynamic_cast 借助 RTTI,用于類型安全的向下轉(zhuǎn)型(Downcasting)。

這四個(gè)關(guān)鍵字的語法格式都是一樣的,具體為:

xxx_cast<newType>(data)

newType 是要轉(zhuǎn)換成的新類型,data 是被轉(zhuǎn)換的數(shù)據(jù)。
例如,老式的C風(fēng)格的 double 轉(zhuǎn) int 的寫法為:

double scores = 95.5;
int n = (int)scores;

C++ 新風(fēng)格的寫法為:

double scores = 95.5;
int n = static_cast<int>(scores);
(3)static_cast

static_cast 是 靜態(tài)轉(zhuǎn)換 的意思,也就是在編譯期間轉(zhuǎn)換,轉(zhuǎn)換失敗的話會(huì)拋出一個(gè)編譯錯(cuò)誤。

static_cast 只能用于良性轉(zhuǎn)換,這樣的轉(zhuǎn)換風(fēng)險(xiǎn)較低,一般不會(huì)發(fā)生什么意外,例如:

(1)基本數(shù)據(jù)類型之間的轉(zhuǎn)換,例如 short 轉(zhuǎn) int、int 轉(zhuǎn) double;

    int m = 10;
    long n1 = m; // int 轉(zhuǎn) long(范圍小的轉(zhuǎn)成范圍大的,無風(fēng)險(xiǎn))
    long n2 = static_cast<long>(m); // 基本數(shù)據(jù)類型的轉(zhuǎn)換可以使用static_cast轉(zhuǎn)換

    char c1 = m; // int 轉(zhuǎn) char(范圍大的轉(zhuǎn)成范圍小的,精度丟失,肯能存在風(fēng)險(xiǎn))
    char c2 = static_cast<char>(m); // 基本數(shù)據(jù)類型的轉(zhuǎn)換可以使用static_cast轉(zhuǎn)換

    cout << n1 << endl;
    cout << n2 << endl;
    cout << c1 << endl;
    cout << c2 << endl;


(2)基類和子類之間的轉(zhuǎn)換

定義兩個(gè)類:Personal 和 Student類,它們之間是繼承關(guān)系,Personal  是基類,Student 是派生類,代碼如下:

class Personal
{
public:
    void setIndex(int index)
    {
        mIndex = index;
    }
    int getIndex()
    {
        return mIndex;
    }
private:
    int mIndex;
};

class Student :public Personal
{
public:
    void setAge(int age)
    {
        mAge = age;
    }
    int getAge()
    {
        return mAge;
    }
private:
    int mAge;
};

基類轉(zhuǎn)派生類是有風(fēng)險(xiǎn)的,而派生類轉(zhuǎn)基類是無風(fēng)險(xiǎn)的;
派生類轉(zhuǎn)基類可以直接向上轉(zhuǎn)型,代碼如下:

Personal* personal = new Student(); // 向上轉(zhuǎn)型,無風(fēng)險(xiǎn)

上轉(zhuǎn)型也可以使用 static_cast 來實(shí)現(xiàn):

Student* student = new Student();
Personal*  personal = static_cast<Personal*>(student);

而基類轉(zhuǎn)派生類屬于向下轉(zhuǎn)型,向下轉(zhuǎn)型存在風(fēng)險(xiǎn),不能使用 static_cast 來轉(zhuǎn)換。

(3)void 指針和具體類型指針相互轉(zhuǎn)換

C風(fēng)格的轉(zhuǎn)換是這樣的:

int* a = new int;
*a = 10;
void* p = a; // int指針轉(zhuǎn)void指針
int* b = (int*)p; // void指針轉(zhuǎn)int指針

如果使用 static_cast 來轉(zhuǎn)換的話,代碼如下:

int* a = new int;
*a = 10;
void* p = static_cast<void*>(a); // int指針轉(zhuǎn)void指針
int* b = static_cast<int*>(p); // void指針轉(zhuǎn)int指針

(4)有轉(zhuǎn)換構(gòu)造函數(shù)的類與其它類型之間的轉(zhuǎn)換

class Book 
{
public:
    Book() {}
    Book(int a):mA(a) {}
    int getA() 
    {
        return mA;
    }
private:
    int mA;
};

class Student
{
public:
    Student(const Book& book)
    {
        mBook = book;
    }

    Book getBook()
    {
        return mBook;
    }
private:
    Book mBook;
};

int main() {

    Book book(10);
    Student student = book;
    cout << student.getBook().getA() << endl;

    return 0;
}

其中,類型轉(zhuǎn)換代碼:

    Student student = book;

可以寫成:

Student student = static_cast<Student>(book);



(5)有類型轉(zhuǎn)換函數(shù)的類與其它類型之間的轉(zhuǎn)換


class Student
{
public:
    Student():mA(10) {}

    operator double() // 強(qiáng)制類型轉(zhuǎn)換運(yùn)算符的重載
    {
        return mA;
    }

    double getA()
    {
        return mA;
    }
private:
    double mA;
};

int main() {

    Student student;
    double n = student;
    cout << n << endl;

    return 0;
}

其中,類型轉(zhuǎn)換代碼:

double n = student;

可以寫成:

double n = static_cast<Student>(student);



需要注意的是,static_cast 不能用于無關(guān)類型之間的轉(zhuǎn)換,因?yàn)檫@些轉(zhuǎn)換都是有風(fēng)險(xiǎn)的,例如:

(1)兩個(gè)具體類型指針之間的轉(zhuǎn)換;
(2)int 和指針之間的轉(zhuǎn)換;
(3)static_cast 也不能用來去掉表達(dá)式的 const 修飾和 volatile 修飾。換句話說,不能將 const/volatile 類型轉(zhuǎn)換為非 const/volatile 類型。
(4)const_cast

const_cast 比較好理解,它用來去掉表達(dá)式的 const 修飾或 volatile 修飾。換句話說,const_cast 就是用來將 const/volatile 類型轉(zhuǎn)換為非 const/volatile 類型。

const 和 volatile 都是用于修飾變量的,所以下面就以 const 為例:

C風(fēng)格的類型轉(zhuǎn)換是:

const int constA = 10;
int* p = (int*) & constA;

使用 const_cast 關(guān)鍵字轉(zhuǎn)換代碼是:

const int constA = 10;
int* p = const_cast<int*>(&constA);
(5)dynamic_cast

dynamic_cast 用于在類的繼承層次之間進(jìn)行類型轉(zhuǎn)換,它既允許向上轉(zhuǎn)型(Upcasting),也允許向下轉(zhuǎn)型(Downcasting)。
向上轉(zhuǎn)型是無條件的,不會(huì)進(jìn)行任何檢測(cè),所以都能成功;
向下轉(zhuǎn)型的前提必須是安全的,要借助 RTTI 進(jìn)行檢測(cè),所有只有一部分能成功。

dynamic_cast 與 static_cast 是相對(duì)的,dynamic_cast 是 動(dòng)態(tài)轉(zhuǎn)換 的意思,static_cast 是 靜態(tài)轉(zhuǎn)換 的意思。
dynamic_cast 會(huì)在程序運(yùn)行期間借助 RTTI 進(jìn)行類型轉(zhuǎn)換,這就要求基類必須包含虛函數(shù);
static_cast 在編譯期間完成類型轉(zhuǎn)換,能夠更加及時(shí)地發(fā)現(xiàn)錯(cuò)誤。

dynamic_cast 的語法格式為:

dynamic_cast <newType> (expression)

newType 和 expression 必須同時(shí)是指針類型或者引用類型。
換句話說,dynamic_cast 只能轉(zhuǎn)換指針類型和引用類型,其它類型(int、double、數(shù)組、類、結(jié)構(gòu)體等)都不行。

對(duì)于指針,如果轉(zhuǎn)換失敗將返回 NULL;
對(duì)于引用,如果轉(zhuǎn)換失敗將拋出 std::bad_cast 異常。

下面開始代碼演示,首先準(zhǔn)備好基類和派生類:

class Personal
{
public:
    virtual void setIndex(int index)
    {
        mIndex = index;
    }
    virtual int getIndex()
    {
        return mIndex;
    }
private:
    int mIndex;
};

class Student :public Personal
{
public:
    void setAge(int age)
    {
        mAge = age;
    }
    int getAge()
    {
        return mAge;
    }
private:
    int mAge;
};

Personal 是基類,Student 是派生類。

【向上轉(zhuǎn)型】 向上轉(zhuǎn)型沒有風(fēng)險(xiǎn)型,可以使用 static_cast 轉(zhuǎn)換,也可以使用 dynamic_cast 轉(zhuǎn)換。

static_cast 向上轉(zhuǎn)型的類型轉(zhuǎn)換已經(jīng)演示過了,下面直接演示使用 dynamic_cast 實(shí)現(xiàn)向上轉(zhuǎn)型。

Student* student = new Student();
Personal* personal = dynamic_cast<Personal*>(student);

【向下轉(zhuǎn)型】 向下轉(zhuǎn)型是一個(gè)比較危險(xiǎn)的動(dòng)作,只能使用 dynamic_cast 實(shí)現(xiàn)。

Personal* personal = new Personal();
Student* personal = dynamic_cast<Student*>(personal);
(6)reinterpret_cast

reinterpret 是 重新解釋 的意思,顧名思義,reinterpret_cast 這種轉(zhuǎn)換僅僅是對(duì)二進(jìn)制位的重新解釋,不會(huì)借助已有的轉(zhuǎn)換規(guī)則對(duì)數(shù)據(jù)進(jìn)行調(diào)整,非常簡(jiǎn)單粗暴,所以風(fēng)險(xiǎn)很高。

reinterpret_cast 可以認(rèn)為是 static_cast 的一種補(bǔ)充,一些 static_cast 不能完成的轉(zhuǎn)換,就可以用 reinterpret_cast 來完成,例如兩個(gè)具體類型指針之間的轉(zhuǎn)換、int 和指針之間的轉(zhuǎn)換(有些編譯器只允許 int 轉(zhuǎn)指針,不允許反過來)。

【舉例一:將 char* 轉(zhuǎn)換為 float*】

char str[] = "zhangsan";
float* p = reinterpret_cast<float*>(str); // 將 char* 轉(zhuǎn)換為 float*
cout << *p << endl;

【舉例二:將 int 轉(zhuǎn)換為 int*】

// 將 int 轉(zhuǎn)換為 int*
int a = 100;
int* p = reinterpret_cast<int*>(a);

【舉例三:類指針轉(zhuǎn)int指針】

class A {
public:
    A(int a = 0, int b = 0) : m_a(a), m_b(b) {}
private:
    int m_a;
    int m_b;
};
int main() {

    A* a = new A(10, 20);
    int* p = reinterpret_cast<int*>(a);
    cout << *p << endl;

    return 0;
}

[本章完...]

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容