在 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;
}
[本章完...]