目錄
1. cast
2. reinterpret_cast?
3. const_cast
3.1 加上const的情況
3.2 去掉const的情況
4. static_cast
4.1 基本類型之間的轉換
4.2 void指針轉換為任意基本類型的指針
4.3 子類和父類之間的轉換
5. dynamic_cast
5.1 RTTI(Run-time Type Identification)
1. cast
英 /kɑ?st/ 美 /k?st/
v. 鑄造;投(釣線);投票;投射(光、影子等);扔;使人懷疑;向…投以(視線、笑容等);分配角色;(蛇)蛻(皮);造謠中傷;踢落;把某人描寫成
n. 鑄件;鑄模;特性;模子;鑄造品;(一出戲劇或一部電影的)全體演員
在C++程序里是一種轉型機制,跟物理鑄造差不多,有一個模子(如int),然后根據這個模子生成一個鑄件。
double a = 1.1;
char* b = reinterpret_cast<char*>(&a);
// char*就是一個新的模具,double指針是原料,通過鑄造case變成一個新的鑄件b
2. reinterpret_cast?
reinterpret
英?/?ri??n?t??pr?t/,??美?/?ri??n?t??rpr?t/
vt.?重新解釋;重新詮釋
reinterpret_cast是四種強制轉換中功能最為強大的(最暴力,最底層,最不安全),跟它的英文釋義一樣重新詮釋。它的本質是編譯器的指令。
它的作用:它可以把一個指針轉換成一個整數,也可以把一個整數轉換成一個指針。或者不同類型的指針的相互替換
代碼示例:
#include <iostream>
int main()
{double a = 1.1;char* b = reinterpret_cast<char*>(&a);double* c = reinterpret_cast<double*>(b);printf("a:%lf, %p\n", a, &a);printf("b:%c, %p\n", *b, b);printf("c:%lf, %p\n", *c, c);
}
輸出:
我嘗試打斷點進入reinterpret_cast內部,發現并不行(它的本質是編譯器的指令)。
通過中間的 char*來轉double*,但是沒有出現精度問題。事實上reinterpret_cast只是在編譯器進行的予以轉化(并未拷貝,只是重新詮釋),只要是個地址就可以轉(二進制拷貝)。
3. const_cast
?有兩個功能,去掉const和加上const。
3.1 加上const的情況
#include <iostream>
int main()
{int* a = new int(1);const int* b = const_cast<const int*>(a);*a = 2;//*b=2;,常量不能修改printf("0x%p,%d\n", a,*a);printf("0x%p,%d\n", b,*b);return 0;
}
輸出:
?
發現值是一樣的,但是地址也是一樣,說白了加const就是加了一個修飾和限定。
3.2 去掉const的情況
#include <iostream>
class A
{
public:int num;A(int val = 100) :num(val) {}~A() {}
};
int main()
{//1.const 修飾指針對象,指向原對象const A* pa1 = new A(200);A* cast_pa1 = const_cast<A*>(pa1);printf("1.const 修飾指針指向對象,指向原對象\n");printf("%p\n", pa1);printf("%p\n", cast_pa1);//2.const 修飾指向指針對象的值,指向原對象A* const pa2 = new A(200);A* cast_pa2 = const_cast<A*>(pa2);printf("2.const 修飾指向對象的值,指向原對象\n");printf("%p\n", pa2);printf("%p\n", cast_pa2);//3.const 同時修飾指針對象和指針對象的值,指向原對象const A* const pa3 = new A(200);A* cast_pa3_1 = const_cast<A*>(pa3);const A* cast_pa3_2 = const_cast<A*>(pa3);A* const cast_pa3_3 = const_cast<A*>(pa3);printf("3.const 同時修飾指針對象和指針對象的值,指向原對象\n");printf("%p\n", pa3);printf("%p\n", cast_pa3_1);printf("%p\n", cast_pa3_2);printf("%p\n", cast_pa3_3);//4.const 修飾普通對象,并且賦值給一般對象,不指向原對象const A pa4;A cast_pa4 = const_cast<A&>(pa4);printf("4.const 修飾普通對象,并且賦值給一般對象\n");printf("%p\n", &pa4);printf("%p\n", &cast_pa4);//5.const 修飾普通對象,并且賦值給引用對象,指向原對象const A pa5;A& cast_pa5 = const_cast<A&>(pa5);printf("5.const 修飾普通對象,并且賦值給引用對象\n");printf("%p\n", &pa5);printf("%p\n", &cast_pa5);// 6. const 修飾對象,對象指針去 const 屬性后賦給指針,指向原對象const A pa6;A* cast_pa6 = const_cast<A*>(&pa6);printf("6. const 修飾對象,對象指針去 const 屬性后賦給指針\n");printf("%p\n", &pa6);printf("%p\n", cast_pa6);//7.const修飾局部變量,不指向原對象const int pa7 = 1;int cast_pa7_1 = const_cast<int&>(pa7);int& cast_pa7_2 = const_cast<int&>(pa7);int* cast_pa7_3 = const_cast<int*>(&pa7);printf("6. const 修飾對象,對象指針去 const 屬性后賦給指針\n");printf("地址, pa7:%p\n", &pa7);printf("cast_pa7_1:%p\n", &cast_pa7_1);printf("cast_pa7_2:%p\n", &cast_pa7_2);printf("cast_pa7_3:%p\n", cast_pa7_3);cast_pa7_1 = 10;printf("pa7:%d,未修改\n", pa7);printf("cast_pa7_1:%d\n", cast_pa7_1);cast_pa7_2 = 100;printf("pa7:%d,未修改\n", pa7);printf("cast_pa7_1:%d\n", cast_pa7_1);printf("cast_pa7_2:%d\n", cast_pa7_2);*cast_pa7_3 = 1000;printf("pa7:%d,未修改\n", pa7);printf("cast_pa7_1:%d\n", cast_pa7_1);printf("cast_pa7_2:%d\n", cast_pa7_2);printf("cast_pa7_3:%d\n", *cast_pa7_3);return 0;
}
輸出
1.const 修飾指針指向對象,指向原對象
016E7820
016E7820
2.const 修飾指向對象的值,指向原對象
016E77C0
016E77C0
3.const 同時修飾指針對象和指針對象的值,指向原對象
016E7850
016E7850
016E7850
016E7850
4.const 修飾普通對象,并且賦值給一般對象
012FFD24
012FFD18
5.const 修飾普通對象,并且賦值給引用對象
012FFD0C
012FFD0C
6. const 修飾對象,對象指針去 const 屬性后賦給指針
012FFCF4
012FFCF4
6. const 修飾對象,對象指針去 const 屬性后賦給指針
地址, pa7:012FFCDC
cast_pa7_1:012FFCD0
cast_pa7_2:012FFCDC
cast_pa7_3:012FFCDC
pa7:1,未修改
cast_pa7_1:10
pa7:1,未修改
cast_pa7_1:10
cast_pa7_2:100
pa7:1,未修改
cast_pa7_1:10
cast_pa7_2:1000
cast_pa7_3:1000
分析:
指針之間的轉換無論怎樣還是原地址
去掉一般對象的const,如果賦值給一般對象則是新對象(A cast_pa4 = const_cast<A &>(pa4);)
去掉內置類型(如int)變量的const,如果賦值給一般對象則是新對象,否則全是原來對象地址(雖然地址是一樣的,但是值是不一樣的)疑惑中。。。。。。
4. static_cast
?三個作用:
1.基本類型之間的轉換
2.void指針轉換為任意基本類型的指針,基本類型指針之間無法使用
3.用于有繼承關系的子類與父類之間的指針或引用的轉換
4.1 基本類型之間的轉換
#include <iostream>
int main()
{double i = 1.1;int a = static_cast<int>(i);double b = static_cast<double>(a);int c = static_cast<int>(b);printf("i:%lf, %p\n", i, &i);printf("a:%d, %p\n", a, &a);printf("b:%lf,%p\n", b, &b);printf("c:%d, %p\n", c, &c);
}
輸出
可以進行基本類型的轉化,但是會損失精度類似與C語言的強制轉化。轉換過程實際上是有內存拷貝的,每次地址都不一樣。跟reinterpret_cast不太一樣reinterpret_cast是底層二進制的強制拷貝和語義轉換不會損失精度。
注:reinterpret_cast不能進行基本類型之間的轉換,只能做指針轉換。
4.2 void指針轉換為任意基本類型的指針
#include <iostream>
int main()
{double a = 1.1;void* b = static_cast<void*>(&a);double* c = static_cast<double*>(b);//int* d = static_cast<int*>(c); // 類型轉換無效(基本類型指針之間無法使用)printf("a:%lf, %p\n", a, &a);printf("b:%p\n", b);printf("c:%lf, %p\n", *c, c);
}
這里是void指針和其他類型的指針進行的轉化,結果是指向的是原地址,跟reinterpret_cast是一樣的結果。說白了static_cast就是能做鐵水變鐵器和鐵器變鐵水,但是不能直接把鐵鐮刀變鐵鋤頭。reinterpret_cast就是可以指鹿為馬,能直接把鐵鐮刀變鐵鋤頭,但是變成的鐵鋤頭能不能刨地就不知道了。這里需要區分普通類型之間的轉換和普通類型指針之間的轉換(普通類型的轉換不是)。
//int* d = static_cast<int*>(c); // 類型轉換無效(基本類型指針之間無法使用)
4.3 子類和父類之間的轉換
#include <iostream>
using namespace std;
class A
{public:A(){};void foo(){cout<<"A!"<<endl;}
};
class B:public A
{public:B(){} ;void foo(){cout<<"B!"<<endl;}
};
int main()
{A *a = new A();B * b = static_cast<B *>(a);b->foo();return 0;
}
輸出:?
這是向下轉型,是不安全的,但是為什么沒有報錯呢,因為B中還沒有B特有的(B的成員變量),A和B兩者在內存中的結構是一致的。
舉個不一致的例子:
#include <iostream>
using namespace std;
class A
{public:A(){}void foo(){cout<<"A!"<<endl;}
};
class B:public A
{char b='c';public:B(){}void foo(){cout<<b<<endl;}
};
int main()
{A* a = new A();a->foo();B* b = static_cast<B*>(a);b->foo();B* pb = new B();pb->foo();return 0;
}
輸出?
分析:這里就發生了錯誤了,B中特有的成員變量沒有初始化(使用了不安全的向下轉型)
5. dynamic_cast
dynamic_cast用于類繼承層次間的指針或引用轉換(主要用于向下的安全轉換)
dynamic_cast向下轉型的安全性主要體現在RTTI
5.1 RTTI(Run-time Type Identification)
運行時類型識別。程序能夠使用基類的指針或引用來檢查著這些指針或引用所指的對象的實際派生類型(判斷指針原型)
RTTI提供了兩個非常有用的操作符:typeid和dynamic_cast。(三個最主要的東西,dynamic_cast,typeid,type_info)
typeid:typeid函數(為type_info類的友元函數,為什么要這樣呢?目的是防止創建type_info對象)的主要作用就是讓用戶知道當前的變量是什么類型的,它可以返回一個type_info的引用,可以獲取類的名稱和編碼typeid重載了type_info中的==和!=可以用于判斷兩個類型是否相等
1)typeid識別靜態類型
當typeid中的操作數是如下情況之一時,typeid運算符指出操作數的靜態類型,即編譯時的類型。
(1)類型名
(2)一個基本類型的變量
(3)一個具體的對象(非指針對象)
(4)一個指向 不含有virtual函數的類 對象的指針的解引用
(5)一個指向 不含有virtual函數的類 對象的引用
靜態類型在程序的運行過程中并不會改變,所以并不需要在程序運行時計算類型,在編譯時就能根據操作數的靜態類型,推導出其類型信息。例如如下的代碼片斷,typeid中的操作數均為靜態類型
#include <iostream>
#include <typeinfo>
using namespace std;
class X {
public:X() { };virtual void func() = 0;
};
class XX : public X {
public:XX() { };void func() { };
};
class Y {
public:Y() { };void func() { };
};
int main()
{int n = 0;XX xx;Y y;Y* py = &y;X* px = &xx;// int和XX都是類型名cout << typeid(int).name() << endl;cout << typeid(XX).name() << endl;// n為基本變量cout << typeid(n).name() << endl;// xx所屬的類雖然存在virtual,但是xx為一個具體的對象cout << typeid(xx).name() << endl;// py為一個指針,屬于基本類型cout << typeid(py).name() << endl;// py指向的Y的對象,但是類Y不存在virtual函數cout << typeid(*py).name() << endl;// pxcout << typeid(*px).name() << endl;cout << typeid(px).name() << endl;return 0;
}
輸出:?
對于px,px是 class X *,但是*px是class XX。
動態類型轉換:
#include <iostream>
#include <typeinfo>
using namespace std;
class X
{
public:X() { mX = 101;}virtual ~X(){}
private:int mX;
};class XX : public X
{
public:XX() :X() { mXX = 1001; }virtual ~XX() { }
private:int mXX;
};class YX : public X
{
public:YX() { mYX = 1002; }virtual ~YX() { }
private:int mYX;
};
int main()
{X x;XX xx;YX yx;// 子類直接轉父類指針,沒問題X* px = &xx;cout << "px:\t" << px << endl;XX* pxx = dynamic_cast<XX*>(px); // 轉換1,成功cout << "pxx:\t" << pxx << endl;YX* pyx = dynamic_cast<YX*>(px); // 轉換2,失敗cout << "pyx:\t" << pyx << endl;pyx = (YX*)px; // 轉換3,成功cout << "pyx:\t" << pyx << endl;pyx = static_cast<YX*>(px); // 轉換4,成功cout << "pyx:\t" << pyx << endl;return 0;
}
輸出:
px是一個基類(X)的指針,但是它指向了派生類XX的一個對象。在轉換1中,轉換成功,因為px指向的對象確實為XX的對象。在轉換2中,轉換失敗,因為px指向的對象并不是一個YX對象,此時dymanic_cast返回nullptr。轉換3為C風格的類型轉換而轉換4使用的是C++中的靜態類型轉換,它們均能成功轉換,但是這個對象實際上并不是一個YX的對象,所以在轉換3和轉換4中,若繼續通過指針使用該對象必然會導致錯誤,所以這個轉換是不安全的。