一起聊聊C++中的四種類型轉(zhuǎn)換符
一:背景
在玩 C 的時候,經(jīng)常會用 void* 來指向一段內(nèi)存地址開端,然后再將其強轉(zhuǎn)成尺度更小的 char* 或 int* 來丈量一段內(nèi)存,參考如下代碼:
int main()
{
void* ptr = malloc(sizeof(int) * 10);
int* int_ptr = (int*)ptr;
char* char_ptr = (char*)ptr;
}
由于 C 的自由度比較大,想怎么玩就怎么玩,帶來的弊端就是容易隱藏著一些不易發(fā)現(xiàn)的bug,歸根到底還是程序員的功底不扎實,C++ 設(shè)計者覺得不能把程序員想的太厲害,應(yīng)該要力所能及的幫助程序員避掉一些不必要的潛在 bug,并且還要盡最大努力的避免對性能有過多的傷害,所以就出現(xiàn)了 4 個強制類型轉(zhuǎn)換運算符。
- const_cast
- reinterpret_cast
- dynamic_cast
- static_cast
既然 C++ 做了歸類,必然就有其各自用途,接下來我們逐一和大家聊一下。
二:理解四大運算符
1. const_cast
這是四個運算符中最好理解的,玩過 C++ 的都知道,默認情況下是不能修改一個 const 變量,比如下面這樣:
int main()
{
const int i = 10;
i = 12;
}
這段代碼肯定是要報錯的,那如果我一定要實現(xiàn)這個功能,如何做呢?這就需要用到 const_cast 去掉它的常量符號,然后對 i 進行操作即可,所以修改代碼如下:
int main()
{
const int i = 10;
auto j = const_cast<int*>(&i);
*(j) = 12;
}

2. reinterpret_cast
從名字上看就是一個 重新解釋轉(zhuǎn)換,很顯然這個非常底層,如果大家玩過 windbg ,應(yīng)該知道用 dt 命令可以將指定的內(nèi)存地址按照某一個結(jié)構(gòu)體丈量出來,比如說 C# 的 CLR 在觸發(fā) GC 時,會有 gc_mechanisms 結(jié)構(gòu),參考代碼如下:
0:000> dt WKS::gc_mechanisms 0x7ffb6ba96e60 coreclr!WKS::gc_mechanisms +0x000 gc_index : 1 +0x008 condemned_generation : 0n0 +0x00c promotion : 0n0 +0x010 compaction : 0n1 +0x014 loh_compaction : 0n0 +0x018 heap_expansion : 0n0 +0x01c concurrent : 0 +0x020 demotion : 0n0 +0x024 card_bundles : 0n1 +0x028 gen0_reduction_count : 0n0 +0x02c should_lock_elevation : 0n0 +0x030 elevation_locked_count : 0n0 +0x034 elevation_reduced : 0n0 +0x038 minimal_gc : 0n0 +0x03c reason : 0 ( reason_alloc_soh ) +0x040 pause_mode : 1 ( pause_interactive ) +0x044 found_finalizers : 0n0 +0x048 background_p : 0n0 +0x04c b_state : 0 ( bgc_not_in_process ) +0x050 allocations_allowed : 0n1 +0x054 stress_induced : 0n0 +0x058 entry_memory_load : 0 +0x05c exit_memory_load : 0
其實 reinterpret_cast 大概也是干這個事的,參考代碼如下:
typedef struct _Point {
int x;
int y;
} Point;
int main()
{
Point point = { 10,11 };
//內(nèi)存地址
void* ptr = &point;
//根據(jù)內(nèi)存地址 丈量出 Point
Point* ptr_point = reinterpret_cast<Point*>(ptr);
printf("x=%d", ptr_point->x);
}
從代碼看,我直接根據(jù) ptr 地址丈量出了 Point 結(jié)構(gòu),說實話這個和 C 玩法就比較類似了。
3. dynamic_cast
在多態(tài)場景下,有時候會遇到這樣的一個問題,一個父類有多個子類,我現(xiàn)在手擁一個父類,我不知道能不能將它轉(zhuǎn)換為其中一個子類,要試探一下看看,那怎么去試探呢? 類似 C# 中的 as 運算符,在 C++ 中就需要用 dynamic_cast 來做這件事情,參考如下:
//點
class Point {
public:
Point(int x, int y) :x(x), y(y) {}
virtual void show() {}
public:
int x;
int y;
};
//矩形
class Rectangle :public Point {
public:
Rectangle(int x, int y, int w, int h) : Point(x, y), w(w), h(h) {}
public:
int w;
int h;
};
//三角形
class Triangle :public Point {
public:
Triangle(int x, int y, int z) :Point(x, y), z(z) {}
public:
int z;
};
int main()
{
Point* p1 = new Rectangle(10, 20, 100, 200);
Point* p2 = new Triangle(4, 5, 6);
//將 p1 轉(zhuǎn)成 子類 Triangle 會報錯的
Triangle* t1 = dynamic_cast<Triangle*>(p1);
if (t1 == nullptr) {
printf("p1 不能轉(zhuǎn)成 Triangle");
}
}

對,場景就是這個,p1 其實是 Rectangle 轉(zhuǎn)上去的, 這時候你肯定是不能將它向下轉(zhuǎn)成 Triangle , 問題就在這里,很多時候你并不知道此時的 p1 是哪一個子類。
接下來的一個問題是,C++ 并不像C# 有元數(shù)據(jù),那它是如何鑒別呢? 其實這用了 RTTI 技術(shù),哪里能看出來呢?哈哈,看匯編啦。
Triangle* t1 = dynamic_cast<Triangle*>(p1); 00831D57 push 0 00831D59 push offset Triangle `RTTI Type Descriptor' (083C150h) 00831D5E push offset Point `RTTI Type Descriptor' (083C138h) 00831D63 push 0 00831D65 mov eax,dword ptr [p1] 00831D68 push eax 00831D69 call ___RTDynamicCast (083104Bh) 00831D6E add esp,14h 00831D71 mov dword ptr [t1],eax
從匯編可以看到編譯器這是帶夾私貨了,在底層偷偷的調(diào)用了一個 ___RTDynamicCast 函數(shù)在運行時幫忙檢測的,根據(jù) cdcel 調(diào)用協(xié)定,參數(shù)是從右到左,恢復(fù)成代碼大概是這樣。
___RTDynamicCast(&p1, 0, &Point, &Triangle,0)
3. static_cast
從名字上就能看出,這個強轉(zhuǎn)具有 static 語義,也就是 編譯階段 就生成好了,具體安全不安全,它就不管了,就拿上面的例子,將 dynamic_cast 改成 static_cast 看看有什么微妙的變化。
int main()
{
Point* p1 = new Rectangle(10, 20, 100, 200);
Point* p2 = new Triangle(4, 5, 6);
Triangle* t1 = static_cast<Triangle*>(p1);
printf("x=%d, y=%d,z=%d", t1->x, t1->y, t1->z);
}

我們發(fā)現(xiàn)居然轉(zhuǎn)成功了,而且 Triangle 的值也是莫名奇怪,直接取了 Rectangle 的前三個值,如果這是生產(chǎn)代碼,肯定要挨批了。。。
接下來簡單看下匯編代碼:
Triangle* t1 = static_cast<Triangle*>(p1);
00DF5B17 mov eax,dword ptr [p1]
00DF5B1A mov dword ptr [t1],eax
printf("x=%d, y=%d,z=%d", t1->x, t1->y, t1->z);
00DF5B1D mov eax,dword ptr [t1]
00DF5B20 mov ecx,dword ptr [eax+0Ch]
00DF5B23 push ecx
00DF5B24 mov edx,dword ptr [t1]
00DF5B27 mov eax,dword ptr [edx+8]
00DF5B2A push eax
00DF5B2B mov ecx,dword ptr [t1]
00DF5B2E mov edx,dword ptr [ecx+4]
00DF5B31 push edx
00DF5B32 push offset string "x=%d, y=%d,z=%d" (0DF8C80h)
00DF5B37 call _printf (0DF145Bh)
00DF5B3C add esp,10h
從代碼中看,它其實就是將 p1 的首地址給了 t1,然后依次把copy偏移值 +4,+8,+0C, 除了轉(zhuǎn)換這個,還可以做一些 int ,long ,double 之間的強轉(zhuǎn),當然也是一樣,編譯時匯編代碼就已經(jīng)生成好了。
到此這篇關(guān)于一起聊聊C++中的四種類型轉(zhuǎn)換符 的文章就介紹到這了,更多相關(guān)C++類型轉(zhuǎn)換符 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++中靜態(tài)存儲區(qū)與棧以及堆的區(qū)別詳解
本篇文章是對C++中靜態(tài)存儲區(qū)與棧以及堆的區(qū)別進行了詳細的分析介紹,需要的朋友參考下2013-05-05
C++ new與malloc和delete及free動態(tài)內(nèi)存管理及區(qū)別介紹
這篇文章主要介紹了深入理解C++中的new/delete和malloc/free動態(tài)內(nèi)存管理,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-12-12
C語言數(shù)據(jù)結(jié)構(gòu)之判斷循環(huán)鏈表空與滿
這篇文章主要介紹了C語言數(shù)據(jù)結(jié)構(gòu)之判斷循環(huán)鏈表空與滿的相關(guān)資料,希望通過本文能幫助到大家,讓大家掌握這部分內(nèi)容,需要的朋友可以參考下2017-10-10
關(guān)于C語言多線程pthread庫的相關(guān)函數(shù)說明
下面小編就為大家?guī)硪黄P(guān)于C語言多線程pthread庫的相關(guān)函數(shù)說明。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05

