本文根據(jù)眾多互聯(lián)網(wǎng)博客內(nèi)容整理后形成颤诀,引用內(nèi)容的版權(quán)歸原始作者所有,僅限于學(xué)習(xí)研究使用籽暇,不得用于任何商業(yè)用途澈段。
首先回顧一下C++類型轉(zhuǎn)換:C++類型轉(zhuǎn)換分為:隱式類型轉(zhuǎn)換和顯式類型轉(zhuǎn)換
第1部分. 隱式類型轉(zhuǎn)換
又稱為“標(biāo)準(zhǔn)轉(zhuǎn)換”,包括以下幾種情況:
- 算術(shù)轉(zhuǎn)換(Arithmetic conversion) : 在混合類型的算術(shù)表達(dá)式中, 最寬的數(shù)據(jù)類型成為目標(biāo)轉(zhuǎn)換類型橄登。
int ival = 3;
double dval = 3.14159;
ival + dval;//ival被提升為double類型
2)一種類型表達(dá)式賦值給另一種類型的對象:目標(biāo)類型是被賦值對象的類型
int *pi = 0; // 0被轉(zhuǎn)化為int *類型
ival = dval; // double->int
例外:void指針賦值給其他指定類型指針時抓歼,不存在標(biāo)準(zhǔn)轉(zhuǎn)換,編譯出錯
3)將一個表達(dá)式作為實(shí)參傳遞給函數(shù)調(diào)用拢锹,此時形參和實(shí)參類型不一致:目標(biāo)轉(zhuǎn)換類型為形參的類型
extern double sqrt(double);
cout << "The square root of 2 is " << sqrt(2) << endl;
//2被提升為double類型:2.0
4)從一個函數(shù)返回一個表達(dá)式谣妻,表達(dá)式類型與返回類型不一致:目標(biāo)轉(zhuǎn)換類型為函數(shù)的返回類型
double difference(int ival1, int ival2)
{
return ival1 - ival2;
//返回值被提升為double類型
}
第2部分. 顯式類型轉(zhuǎn)換
被稱為“強(qiáng)制類型轉(zhuǎn)換”(cast)
C 風(fēng)格 | (type-id) |
---|---|
C++風(fēng)格 | static_cast、dynamic_cast面褐、reinterpret_cast拌禾、和const_cast.. |
關(guān)于強(qiáng)制類型轉(zhuǎn)換的問題,很多書都討論過展哭,寫的最詳細(xì)的是C++ 之父的《C++ 的設(shè)計(jì)和演化》湃窍。最好的解決方法就是不要使用C風(fēng)格的強(qiáng)制類型轉(zhuǎn)換,而是使用標(biāo)準(zhǔn)C++的類型轉(zhuǎn)換符:static_cast, dynamic_cast匪傍。標(biāo)準(zhǔn)C++中有四個類型轉(zhuǎn)換符:static_cast您市、dynamic_cast、reinterpret_cast役衡、和const_cast茵休。下面對它們一一進(jìn)行介紹。
static_cast
用法:**static_cast **< type-id > ( expression )
說明:該運(yùn)算符把expression轉(zhuǎn)換為type-id類型手蝎,但沒有運(yùn)行時類型檢查來保證轉(zhuǎn)換的安全性榕莺。
來源:為什么需要static_cast強(qiáng)制轉(zhuǎn)換?
1:void*指針->其他類型指針情況
2:改變通常的標(biāo)準(zhǔn)轉(zhuǎn)換情況
3:避免出現(xiàn)可能多種轉(zhuǎn)換的歧義
它主要有如下幾種用法:
- 用于類層次結(jié)構(gòu)中基類和子類之間指針或引用的轉(zhuǎn)換棵介。進(jìn)行上行轉(zhuǎn)換(把子類的指針或引用轉(zhuǎn)換成基類表示)是安全的钉鸯;進(jìn)行下行轉(zhuǎn)換(把基類指針或引用轉(zhuǎn)換成子類指針或引用)時,由于沒有動態(tài)類型檢查邮辽,所以是不安全的唠雕。
- 用于基本數(shù)據(jù)類型之間的轉(zhuǎn)換,如把int轉(zhuǎn)換成char吨述,把int轉(zhuǎn)換成enum岩睁。這種轉(zhuǎn)換的安全性也要開發(fā)人員來保證。
- 把void指針轉(zhuǎn)換成目標(biāo)類型的指針*(不安全!!)
- 把任何類型的表達(dá)式轉(zhuǎn)換成void*類型揣云。
注意:static_cast不能轉(zhuǎn)換掉expression的const捕儒、volitale、或者_(dá)_unaligned屬性邓夕。
dynamic_cast
用法:**dynamic_cast **< type-id > ( expression )
說明:該運(yùn)算符把expression轉(zhuǎn)換成type-id類型的對象刘莹。Type-id必須是類的指針亿笤、類的引用或者void *;如果type-id是類指針類型栋猖,那么expression也必須是一個指針,如果type-id是一個引用汪榔,那么expression也必須是一個引用蒲拉。
來源:為什么需要dynamic_cast強(qiáng)制轉(zhuǎn)換?
簡單的說痴腌,當(dāng)無法使用virtual函數(shù)的時候
典型案例:X公司提供給我們一個類庫雌团,其中提供一個類Employee.以頭文件Eemployee.h和類庫.lib分發(fā)給用戶顯然我們并無法得到類的實(shí)現(xiàn)的源碼
//Emplyee.h
class Employee
{
public:
virtual int salary();
};
class Manager : public Employee
{
public:
int salary();
};
class Programmer : public Employee
{
public:
int salary();
};
Y公司在開發(fā)的時候建立有如下類:
class MyCompany
{
public:
void payroll(Employee *pe);
//...
};
void MyCompany::payroll(Employee *pe)
{
//do something
}
但是開發(fā)到后期,我們希望能增加一個bonus()的成員函數(shù)到X公司提供的類層次中士聪。假設(shè)我們知道源代碼的情況下锦援,很簡單,增加虛函數(shù):
//Emplyee.h
class Employee
{
public:
virtual int salary();
virtual int bonus();
};
class Manager : public Employee
{
public:
int salary();
};
class Programmer : public Employee
{
public:
int salary();
int bonus();
};
//Emplyee.cpp
...
int Programmer::bonus()
{
//...
}
...
payroll()通過多態(tài)來調(diào)用bonus()
class MyCompany
{
public:
void payroll(Employee *pe);
};
void MyCompany::payroll(Employee *pe)
{
//do something
//pe->bonus();
}
但是現(xiàn)在情況是剥悟,我們并不能修改源代碼灵寺,怎么辦?dynamic_cast華麗登場了区岗!
在Employee.h中增加bonus()聲明略板,在另一個地方定義此函數(shù),修改調(diào)用函數(shù)payroll().重新編譯慈缔,ok
//Emplyee.h
class Employee
{
public:
virtual int salary();
};
class Manager : public Employee
{
public:
int salary();
};
class Programmer : public Employee
{
public:
int salary();
int bonus();//直接在這里擴(kuò)展
};
//somewhere.cpp
int Programmer::bonus()
{
//define...
}
class MyCompany
{
public:
void payroll(Employee *pe);
//...
};
void MyCompany::payroll(Employee *pe)
{
Programmer *pm = dynamic_cast<Programmer *>(pe);
//如果pe實(shí)際指向一個Programmer對象,dynamic_cast成功叮称,并且開始指向Programmer對象起始處
if(pm)
{
//call Programmer::bonus()
}
//如果pe不是實(shí)際指向Programmer對象,dynamic_cast失敗藐鹤,并且pm = 0
else
{
//use Employee member functions
}
}
dynamic_cast主要用于類層次間的上行轉(zhuǎn)換和下行轉(zhuǎn)換瓤檐,還可以用于類之間的交叉轉(zhuǎn)換。在類層次間進(jìn)行上行轉(zhuǎn)換時娱节,dynamic_cast和static_cast的效果是一樣的挠蛉;在進(jìn)行下行轉(zhuǎn)換時,dynamic_cast具有類型檢查的功能括堤,比static_cast更安全碌秸。
class Base
{
public:
int m_iNum;
virtual void foo();
};
class Derived:public Base
{
public:
char *m_szName[100];
};
void func(Base *pb)
{
Derived *pd1 = static_cast<Derived *>(pb);
Derived *pd2 = dynamic_cast<Derived *>(pb);
}
在上面的代碼段中,
如果pb實(shí)際指向一個Derived類型的對象悄窃,pd1和pd2是一樣的讥电,并且對這兩個指針執(zhí)行Derived類型的任何操作都是安全的;
如果pb實(shí)際指向的是一個Base類型的對象轧抗,那么pd1將是一個指向該對象的指針恩敌,對它進(jìn)行Derived類型的操作將是不安全的(如訪問m_szName),而pd2將是一個空指針(即0横媚,因?yàn)閐ynamic_cast失敗)纠炮。
另外要注意:Base要有虛函數(shù)月趟,否則會編譯出錯;static_cast則沒有這個限制恢口。這是由于運(yùn)行時類型檢查需要運(yùn)行時類型信息孝宗,而這個信息存儲在類的虛函數(shù)表(關(guān)于虛函數(shù)表的概念,詳細(xì)可見<Inside c++ object model>)中耕肩,只有定義了虛函數(shù)的類才有虛函數(shù)表因妇,沒有定義虛函數(shù)的類是沒有虛函數(shù)表的。
另外猿诸,dynamic_cast還支持交叉轉(zhuǎn)換(cross cast)婚被。如下代碼所示。
class Base
{
public:
int m_iNum;
virtual void f(){}
};
class Derived1 : public Base{};
class Derived2 : public Base{};
void foo(){
derived1 *pd1 = new Drived1;
pd1->m_iNum = 100;
Derived2 *pd2 = static_cast<Derived2 *>(pd1); //compile error
Derived2 *pd2 = dynamic_cast<Derived2 *>(pd1); //pd2 is NULL
delete pd1;
}
在函數(shù)foo中梳虽,使用static_cast進(jìn)行轉(zhuǎn)換是不被允許的址芯,將在編譯時出錯;而使用 dynamic_cast的轉(zhuǎn)換則是允許的窜觉,結(jié)果是空指針谷炸。
reinpreter_cast
用法:reinpreter_cast<type-id> (expression)
說明:type-id必須是一個指針、引用禀挫、算術(shù)類型淑廊、函數(shù)指針或者成員指針。它可以把一個指針轉(zhuǎn)換成一個整數(shù)特咆,也可以把一個整數(shù)轉(zhuǎn)換成一個指針(先把一個指針轉(zhuǎn)換成一個整數(shù)季惩,在把該整數(shù)轉(zhuǎn)換成原類型的指針,還可以得到原先的指針值)腻格。
IBM的C++指南里倒是明確告訴了我們reinterpret_cast可以画拾,或者說應(yīng)該在什么地方用來作為轉(zhuǎn)換運(yùn)算符:
- 從指針類型到一個足夠大的整數(shù)類型
- 從整數(shù)類型或者枚舉類型到指針類型
- 從一個指向函數(shù)的指針到另一個不同類型的指向函數(shù)的指針
- 從一個指向?qū)ο蟮闹羔樀搅硪粋€不同類型的指向?qū)ο蟮闹羔?/li>
- 從一個指向類函數(shù)成員的指針到另一個指向不同類型的函數(shù)成員的指針
- 從一個指向類數(shù)據(jù)成員的指針到另一個指向不同類型的數(shù)據(jù)成員的指針
const_cast
用法:const_cast<type_id> (expression)
說明:該運(yùn)算符用來修改類型的const或volatile屬性。除了const 或volatile修飾之外菜职, type_id和expression的類型是一樣的青抛。
常量指針被轉(zhuǎn)化成非常量指針,并且仍然指向原來的對象酬核;常量引用被轉(zhuǎn)換成非常量引用蜜另,并且仍然指向原來的對象;常量對象被轉(zhuǎn)換成非常量對象嫡意。
Voiatile和const類試举瑰。舉如下一例:
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
}
上面的代碼編譯時會報(bào)錯,因?yàn)閎1是一個常量對象蔬螟,不能對它進(jìn)行改變此迅;使用const_cast把它轉(zhuǎn)換成一個常量對象,就可以對它的數(shù)據(jù)成員任意改變。注意:b1和b2是兩個不同的對象耸序。
父類子類指針相互轉(zhuǎn)換問題
1:當(dāng)自己的類指針指向自己類的對象時忍些,無論調(diào)用的是虛函數(shù)還是實(shí)函數(shù),其調(diào)用的都是自己的:
2:當(dāng)指向父類對象的父類指針被強(qiáng)制轉(zhuǎn)換成子類指針時候坎怪,子類指針調(diào)用函數(shù)時罢坝,只有非重寫函數(shù)是自己的,虛函數(shù)是父類的搅窿;
3:當(dāng)指向子類對象的子類指針被強(qiáng)制轉(zhuǎn)換成父類指針的時候炸客,也就是父類指針指向子類對象,此時戈钢,父類指針調(diào)用的虛函數(shù)都是子類的,而非虛函數(shù)都是自己的是尔;
將上面三句話總結(jié)成一句話就是:
當(dāng)父類子類有同名非虛函數(shù)的時候殉了,調(diào)用的是轉(zhuǎn)換后的指針類型的函數(shù);
當(dāng)父類子類有同名虛函數(shù)的時候呢拟枚,調(diào)用的是指針轉(zhuǎn)換前指向的對象類型的函數(shù)薪铜。
詳見以下代碼:
#include <iostream>
using namespace std;
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
void h() { cout << "Base::h" << endl; }
};
class Derived:public Base
{
public:
virtual void f(){cout<<"Derived::f"<<endl;}
void g(){cout<<"Derived::g"<<endl;}
void h(){cout<<"Derived::h"<<endl;}
};
void main()
{
Base *pB=new Base();
cout<<"當(dāng)基類指針指向基類對象時:"<<endl;
pB->f();
pB->g();
pB->h();
cout<<endl;
Derived *pD=(Derived*)pB;
cout<<"當(dāng)父類指針被強(qiáng)制轉(zhuǎn)換成子類指針時:"<<endl;
pD->f();
pD->g();
pD->h();
cout<<endl;
Derived *pd=new Derived();
cout<<"當(dāng)子類指針指向子類時候"<<endl;
pd->f();
pd->g();
pd->h();
cout<<endl;
Base *pb=(Base *)pd;
cout<<"當(dāng)子類指針被強(qiáng)制轉(zhuǎn)換成父類指針時:"<<endl;
pb->f();
pb->g();
pb->h();
cout<<endl;
Base *pp=new Derived();
cout<<"父類指針指向子類對象時候:"<<endl;
pp->f();
pp->g();
pp->h();
cout<<endl;
}
執(zhí)行結(jié)果如下圖:參考資料
C++標(biāo)準(zhǔn)轉(zhuǎn)換運(yùn)算符reinterpret_cast
c++中四種強(qiáng)制類型轉(zhuǎn)換
【C++專題】static_cast, dynamic_cast, const_cast探討