面試cast:reinterpret_cast/const_cast/static_cast/dynamic_cast

目錄

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中,若繼續通過指針使用該對象必然會導致錯誤,所以這個轉換是不安全的。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/164249.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/164249.shtml
英文地址,請注明出處:http://en.pswp.cn/news/164249.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Selenium實現多頁面切換

當使用 Selenium 進行自動化測試或爬取數據時&#xff0c;有時需要處理多個頁面之間的切換。以下是一些可能需要多頁面切換的情況&#xff1a; 1、打開新窗口/頁面&#xff1a; 在當前頁面上點擊鏈接、按鈕或執行某些操作時&#xff0c;可能會打開一個新的窗口或頁面。此時&a…

【element優化經驗】怎么讓element-ui中表單多語言切換排版不亂

目錄 前言&#xff1a; 痛點&#xff1a; 1.左對齊&#xff0c;右對齊在中文和外語情況下字數不同&#xff0c;固定寬度會使名稱換行&#xff0c;不在整行對齊&#xff0c;影響美觀。 2.如果名稱和輸入框不在一行&#xff0c;會使頁面越來越長 3.label-width值給變量&#…

隨筆記錄-springmvc_ResourceHandlerRegistry+ResourceHttpRequestHandler

環境&#xff1a;springboot-2.7.5 配置文件配置靜態資源映射 springboot配置靜態資源映射方式是通過 WebMvcAutoConfiguration 實現的 spring: # resources: # # 自springboot 2.5.5之后&#xff0c;該屬性已經被廢棄&#xff0c;使用spring.web.resources.static-locat…

爬蟲逆向你應該懂得Javascript知識

背景 大家在學習爬蟲逆向的時候&#xff0c;一般都會涉及到對js源文件進行代碼扣去&#xff0c;但是有的時候&#xff0c;你最好有js基礎&#xff0c;能發現加密或者解密在那個位置&#xff0c;或者是能用python改寫js代碼&#xff0c;這就對個人的Javascript的能力有一定要求…

Switch的使用及其注意事項

注意第五點要看清&#xff0c;case執行完后匹配沒有成功&#xff0c;如過有Default&#xff0c;將會執行Default&#xff0c;如果有case在Default之后&#xff0c;而且Default沒有break語句&#xff0c;那么將會繼續執行case的語句&#xff0c;此時case中的常量表達式只起語句標…

【Skynet 入門實戰練習】游戲模塊劃分 | 基礎功能模塊 | timer 定時器模塊 | logger 日志服務模塊

文章目錄 游戲模塊基礎功能模塊定時器模塊日志模塊通用模塊 游戲模塊 游戲從邏輯方面可以分為下面幾個模塊&#xff1a; 注冊和登錄網絡協議數據庫玩法邏輯其他通用模塊 除了邏輯劃分&#xff0c;還有幾個重要的工具類模塊&#xff1a; Excel 配置導表工具GM 指令測試機器人…

系列一、Spring整合MyBatis不忽略mapper接口同目錄的xxxMapper.xml

一、概述 默認情況下maven要求我們將xml配置、properties配置等都放在resources目錄下&#xff0c;如果我們強行將其放在java目錄&#xff0c;即將xxxMapper.xml和xxxMapper接口放在同一個目錄下&#xff0c;那么默認情況下maven打包時會將這個xxxMapper.xml文件忽略掉&#xf…

【辦公常識_1】寫好的代碼如何上傳?使用svn commit

首先找到對應的目錄 找到文件之后點擊SVN Commit

【標注數據】labelme的安裝與使用

這里寫目錄標題 下載標數據 下載 標數據 打開自動保存 創建矩形

NSGA-II求解微電網多目標優化調度(MATLAB)

一、NSGA-II簡介 NSGA-Ⅱ算法是Kalyanmoy Deb等人于 2002年在 NSGA 的基礎上提出的&#xff0c;它比 NSGA算法更加優越&#xff1a;它采用了快速非支配排序算法&#xff0c;計算復雜度比 NSGA 大大的降低&#xff1b;采用了擁擠度和擁擠度比較算子&#xff0c;代替了需要指定的…

Design Guidelines for 100 Gbps

文章目錄 Stratix V GT Transceiver ChannelsCFP2 Host Connector Assembly and PinoutStratix V GT to CFP2 Interface Layout DesignBoard Stack Up DimensionsExample Design Channel PerformanceSimulation Results for Stratix V GT to CFP2 Connector Layout Design Desi…

特征工程完整指南 - 第二部分

蘇米特班迪帕迪亞 照片由Dan Cristian P?dure?在Unsplash上拍攝 一、說明 DATA&#xff0c;通常被稱為原油&#xff0c;需要經過加工和清潔才能有效地用于各種用途。正如我們不直接使用來自其來源的石油一樣&#xff0c;數據也經過類似的處理以提取其真正價值。 二、特征選…

LabVIEW中如何達到NI SMU最大采樣率

LabVIEW中如何達到NI SMU最大采樣率 NISMU的數字化儀功能對于捕獲SMU詳細的瞬態響應特性或表征待測設備&#xff08;DUT&#xff09;響應&#xff08;例如線性調整率和負載調整率&#xff09;至關重要。沒有此功能&#xff0c;將需要一個外部示波器。 例如&#xff0c;假設在…

Docker start/stop/restart 命令

docker start&#xff1a;啟動一個或多個已經被停止的容器。 docker stop&#xff1a;停止一個運行中的容器。 docker restart&#xff1a;重啟容器。 語法 docker start [OPTIONS] CONTAINER [CONTAINER...]docker stop [OPTIONS] CONTAINER [CONTAINER...]docker restart…

設計循環隊列(詳解)

呀哈嘍&#xff0c;我是結衣 今天給大家帶來的內容如標題所述&#xff0c;我們來設計環形隊列&#xff0c;雖然隊列沒有講&#xff0c;但是我就是想講啊。那么環形隊列現在開始。 隊列的屬性 在設計環形隊列前&#xff0c;我們先要了解隊列的特點&#xff08;先進先出&#x…

鴻蒙(HarmonyOS)應用開發——ArkTs學習準備

介紹 前面我們已經介紹了&#xff0c;如何安裝HarmonyOS的IDE ,那么現在我們來介紹一下。HarmonyOS 開發的語言——ArkTs. ArkTS 是HarmonyOS的開發語言&#xff0c;他是typescript 的擴展&#xff0c;而typesrcipt是javascript的超集&#xff0c;如果你不太熟悉typescript語法…

qml Loader使用介紹

QML Loader 是 Qt Quick 框架中的一個元素,它允許你動態地加載和卸載 QML 組件。Loader 的作用主要體現在以下幾個方面: 延遲加載:Loader 允許你在需要時才加載組件,而不是在應用程序啟動時一次性加載所有組件。這樣可以加快應用程序的啟動時間,因為它只需要初始化用戶當前…

MIT_線性代數筆記:列空間和零空間

目錄 前言子空間綜述列空間 Column space零空間&#xff08;或化零空間&#xff09;Nullspaceb 值的影響 Other values of b 前言 本節繼續研究子空間&#xff0c;特別是矩陣的列空間&#xff08;column space&#xff09;和零空間&#xff08;nullspace&#xff09;。 子空間…

FreeRTOS的并行與并發思考

FreeRTOS的任務觸發是由滴答時鐘觸發SysTick中斷來觸發調度器執行或阻塞或掛起和切換任務的。 首先是任務的并發能力&#xff0c;FreeRTOS的任務執行是基于全搶占調度機制&#xff0c;任務優先級按在就緒列表中由高到低排布&#xff0c;系統首先執行最高優先級任務&#xff0c;…