C++中的四種類型轉換運算符

隱式類型轉換是安全的,顯式類型轉換是有風險的,C語言之所以增加強制類型轉換的語法,就是為了強調風險,讓程序員意識到自己在做什么。但是,這種強調風險的方式還是比較粗放,粒度比較大,它并沒有表明存在什么風險,風險程度如何。

再者,C風格的強制類型轉換統一使用( ),而( )在代碼中隨處可見,所以也不利于使用文本檢索工具(例如 Windows 下的 Ctrl+F、Linux 下的 grep 命令、Mac 下的 Command+F)定位關鍵代碼。為了使潛在風險更加細化,使問題追溯更加方便,使書寫格式更加規范,C++ 對類型轉換進行了分類,并新增了四個關鍵字來予以支持,它們分別是:

這四個關鍵字的語法格式都是一樣的,具體為:

xxx_cast<newType>(data)

newType 是要轉換成的新類型,data 是被轉換的數據。例如,老式的C風格的 double 轉 int 的寫法為:

    double scores = 95.5;int n = (int)scores;

C++ 新風格的寫法為:

    double scores = 95.5;int n = static_cast<int>(scores);

static_cast 關鍵字

static_cast 只能用于良性轉換,這樣的轉換風險較低,一般不會發生什么意外,例如:

  • 原有的自動類型轉換,例如 short 轉 int、int 轉 double、const 轉非 const、向上轉型等;

  • void 指針和具體類型指針之間的轉換,例如void *int *char *void *等;

  • 有轉換構造函數或者類型轉換函數的類與其它類型之間的轉換,例如 double 轉 Complex(調用轉換構造函數)、Complex 轉 double(調用類型轉換函數)。

需要注意的是,static_cast 不能用于無關類型之間的轉換,因為這些轉換都是有風險的,例如:

  • 兩個具體類型指針之間的轉換,例如int *double *Student *int *等。不同類型的數據存儲格式不一樣,長度也不一樣,用 A 類型的指針指向 B 類型的數據后,會按照 A 類型的方式來處理數據:如果是讀取操作,可能會得到一堆沒有意義的值;如果是寫入操作,可能會使 B 類型的數據遭到破壞,當再次以 B 類型的方式讀取數據時會得到一堆沒有意義的值。

  • int 和指針之間的轉換。將一個具體的地址賦值給指針變量是非常危險的,因為該地址上的內存可能沒有分配,也可能沒有讀寫權限,恰好是可用內存反而是小概率事件。

static_cast 也不能用來去掉表達式的 const 修飾和?volatile 修飾。換句話說,不能將 const/volatile 類型轉換為非 const/volatile 類型。static_cast 是“靜態轉換”的意思,也就是在編譯期間轉換,轉換失敗的話會拋出一個編譯錯誤。下面的代碼演示了 static_cast 的正確用法和錯誤用法:

    #include <iostream>#include <cstdlib>using namespace std;class Complex{public:Complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag){ }public:operator double() const { return m_real; }  //類型轉換函數private:double m_real;double m_imag;};int main(){//下面是正確的用法int m = 100;Complex c(12.5, 23.8);long n = static_cast<long>(m);  //寬轉換,沒有信息丟失char ch = static_cast<char>(m);  //窄轉換,可能會丟失信息int *p1 = static_cast<int*>( malloc(10 * sizeof(int)) );  //將void指針轉換為具體類型指針void *p2 = static_cast<void*>(p1);  //將具體類型指針,轉換為void指針double real= static_cast<double>(c);  //調用類型轉換函數//下面的用法是錯誤的float *p3 = static_cast<float*>(p1);  //不能在兩個具體類型的指針之間進行轉換p3 = static_cast<float*>(0X2DF9);  //不能將整數轉換為指針類型return 0;}

const_cast 關鍵字

const_cast 比較好理解,它用來去掉表達式的 const 修飾或 volatile 修飾。換句話說,const_cast 就是用來將 const/volatile 類型轉換為非 const/volatile 類型。

下面我們以 const 為例來說明 const_cast 的用法:

    #include <iostream>using namespace std;int main(){const int n = 100;int *p = const_cast<int*>(&n);*p = 234;cout<<"n = "<<n<<endl;cout<<"*p = "<<*p<<endl;return 0;}

運行結果:

n = 100

*p = 234

&n用來獲取 n 的地址,它的類型為const int *,必須使用 const_cast 轉換為int *類型后才能賦值給 p。由于 p 指向了 n,并且 n 占用的是棧內存,有寫入權限,所以可以通過 p 修改 n 的值。有讀者可能會問,為什么通過 n 和 *p 輸出的值不一樣呢?這是因為 C++ 對常量的處理更像是編譯時期的#define,是一個值替換的過程,代碼中所有使用 n 的地方在編譯期間就被替換成了 100。換句話說,第 8 行代碼被修改成了下面的形式:

cout<<"n = "<<100<<endl;

這樣以來,即使程序在運行期間修改 n 的值,也不會影響 cout 語句了。更多關于 const 的內容請猛擊《C++中的const又玩出了新花樣》。使用 const_cast 進行強制類型轉換可以突破 C/C++ 的常數限制,修改常數的值,因此有一定的危險性;但是程序員如果這樣做的話,基本上會意識到這個問題,因此也還有一定的安全性。

reinterpret_cast 關鍵字

reinterpret 是“重新解釋”的意思,顧名思義,reinterpret_cast 這種轉換僅僅是對二進制位的重新解釋,不會借助已有的轉換規則對數據進行調整,非常簡單粗暴,所以風險很高。

reinterpret_cast 可以認為是 static_cast 的一種補充,一些 static_cast 不能完成的轉換,就可以用?reinterpret_cast 來完成,例如兩個具體類型指針之間的轉換、int 和指針之間的轉換(有些編譯器只允許 int 轉指針,不允許反過來)。

下面的代碼代碼演示了 reinterpret_cast 的使用:

    #include <iostream>using namespace std;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(){//將 char* 轉換為 float*char str[]="http://c.biancheng.net";float *p1 = reinterpret_cast<float*>(str);cout<<*p1<<endl;//將 int 轉換為 int*int *p = reinterpret_cast<int*>(100);//將 A* 轉換為 int*p = reinterpret_cast<int*>(new A(25, 96));cout<<*p<<endl;return 0;}

運行結果:3.0262e+2925可以想象,用一個 float 指針來操作一個 char 數組是一件多么荒誕和危險的事情,這樣的轉換方式不到萬不得已的時候不要使用。將A*轉換為int*,使用指針直接訪問 private 成員刺穿了一個類的封裝性,更好的辦法是讓類提供 get/set 函數,間接地訪問成員變量。

dynamic_cast 關鍵字

dynamic_cast 用于在類的繼承層次之間進行類型轉換,它既允許向上轉型(Upcasting),也允許向下轉型(Downcasting)。向上轉型是無條件的,不會進行任何檢測,所以都能成功;向下轉型的前提必須是安全的,要借助 RTTI 進行檢測,所有只有一部分能成功。

dynamic_cast 與 static_cast 是相對的,dynamic_cast 是“動態轉換”的意思,static_cast 是“靜態轉換”的意思。dynamic_cast 會在程序運行期間借助 RTTI 進行類型轉換,這就要求基類必須包含虛函數;static_cast 在編譯期間完成類型轉換,能夠更加及時地發現錯誤。

dynamic_cast 的語法格式為:

dynamic_cast <newType> (expression)

newType 和 expression 必須同時是指針類型或者引用類型。換句話說,dynamic_cast 只能轉換指針類型和引用類型,其它類型(int、double、數組、類、結構體等)都不行。對于指針,如果轉換失敗將返回 NULL;對于引用,如果轉換失敗將拋出std::bad_cast異常。

1) 向上轉型(Upcasting)

向上轉型時,只要待轉換的兩個類型之間存在繼承關系,并且基類包含了虛函數(這些信息在編譯期間就能確定),就一定能轉換成功。因為向上轉型始終是安全的,所以 dynamic_cast 不會進行任何運行期間的檢查,這個時候的 dynamic_cast 和 static_cast 就沒有什么區別了。

「向上轉型時不執行運行期檢測」雖然提高了效率,但也留下了安全隱患,請看下面的代碼:

    #include <iostream>#include <iomanip>using namespace std;class Base{public:Base(int a = 0): m_a(a){ }int get_a() const{ return m_a; }virtual void func() const { }protected:int m_a;};class Derived: public Base{public:Derived(int a = 0, int b = 0): Base(a), m_b(b){ }int get_b() const { return m_b; }private:int m_b;};int main(){//情況①Derived *pd1 = new Derived(35, 78);Base *pb1 = dynamic_cast<Derived*>(pd1);cout<<"pd1 = "<<pd1<<", pb1 = "<<pb1<<endl;cout<<pb1->get_a()<<endl;pb1->func();//情況②int n = 100;Derived *pd2 = reinterpret_cast<Derived*>(&n);Base *pb2 = dynamic_cast<Base*>(pd2);cout<<"pd2 = "<<pd2<<", pb2 = "<<pb2<<endl;cout<<pb2->get_a()<<endl;  //輸出一個垃圾值pb2->func();  //內存錯誤return 0;}

情況①是正確的,沒有任何問題。對于情況②,pd 指向的是整型變量 n,并沒有指向一個 Derived 類的對象,在使用 dynamic_cast 進行類型轉換時也沒有檢查這一點,而是將 pd 的值直接賦給了 pb(這里并不需要調整偏移量),最終導致 pb 也指向了 n。因為 pb 指向的不是一個對象,所以get_a()得不到 m_a 的值(實際上得到的是一個垃圾值),pb2->func()也得不到 func() 函數的正確地址。

pb2->func()得不到 func() 的正確地址的原因在于,pb2 指向的是一個假的“對象”,它沒有虛函數表,也沒有虛函數表指針,而 func() 是虛函數,必須到虛函數表中才能找到它的地址。

2) 向下轉型(Downcasting)

向下轉型是有風險的,dynamic_cast 會借助 RTTI 信息進行檢測,確定安全的才能轉換成功,否則就轉換失敗。那么,哪些向下轉型是安全地呢,哪些又是不安全的呢?下面我們通過一個例子來演示:

    #include <iostream>using namespace std;class A{public:virtual void func() const { cout<<"Class A"<<endl; }private:int m_a;};class B: public A{public:virtual void func() const { cout<<"Class B"<<endl; }private:int m_b;};class C: public B{public:virtual void func() const { cout<<"Class C"<<endl; }private:int m_c;};class D: public C{public:virtual void func() const { cout<<"Class D"<<endl; }private:int m_d;};int main(){A *pa = new A();B *pb;C *pc;//情況①pb = dynamic_cast<B*>(pa);  //向下轉型失敗if(pb == NULL){cout<<"Downcasting failed: A* to B*"<<endl;}else{cout<<"Downcasting successfully: A* to B*"<<endl;pb -> func();}pc = dynamic_cast<C*>(pa);  //向下轉型失敗if(pc == NULL){cout<<"Downcasting failed: A* to C*"<<endl;}else{cout<<"Downcasting successfully: A* to C*"<<endl;pc -> func();}cout<<"-------------------------"<<endl;//情況②pa = new D();  //向上轉型都是允許的pb = dynamic_cast<B*>(pa);  //向下轉型成功if(pb == NULL){cout<<"Downcasting failed: A* to B*"<<endl;}else{cout<<"Downcasting successfully: A* to B*"<<endl;pb -> func();}pc = dynamic_cast<C*>(pa);  //向下轉型成功if(pc == NULL){cout<<"Downcasting failed: A* to C*"<<endl;}else{cout<<"Downcasting successfully: A* to C*"<<endl;pc -> func();}return 0;}

運行結果:

Downcasting failed: A* to B*

Downcasting failed: A* to C*

-------------------------

Downcasting successfully: A* to B*

ClassD

Downcasting successfully: A* to C*

Class D

這段代碼中類的繼承順序為:A --> B --> C --> D。pa 是A*類型的指針,當 pa 指向 A 類型的對象時,向下轉型失敗,pa 不能轉換為B*C*類型。當 pa 指向 D 類型的對象時,向下轉型成功,pa 可以轉換為B*C*類型。同樣都是向下轉型,為什么 pa 指向的對象不同,轉換的結果就大相徑庭呢?

在《C++ RTTI機制下的對象內存模型(透徹)》一節中,我們講到了有虛函數存在時對象的真實內存模型,并且也了解到,每個類都會在內存中保存一份類型信息,編譯器會將存在繼承關系的類的類型信息使用指針“連接”起來,從而形成一個繼承鏈(Inheritance Chain),也就是如下圖所示的樣子:

當使用 dynamic_cast 對指針進行類型轉換時,程序會先找到該指針指向的對象,再根據對象找到當前類(指針指向的對象所屬的類)的類型信息,并從此節點開始沿著繼承鏈向上遍歷,如果找到了要轉化的目標類型,那么說明這種轉換是安全的,就能夠轉換成功,如果沒有找到要轉換的目標類型,那么說明這種轉換存在較大的風險,就不能轉換。

對于本例中的情況①,pa 指向 A 類對象,根據該對象找到的就是 A 的類型信息,當程序從這個節點開始向上遍歷時,發現 A 的上方沒有要轉換的 B 類型或 C 類型(實際上 A 的上方沒有任何類型了),所以就轉換敗了。對于情況②,pa 指向 D 類對象,根據該對象找到的就是 D 的類型信息,程序從這個節點向上遍歷的過程中,發現了 C 類型和 B 類型,所以就轉換成功了。

總起來說,dynamic_cast 會在程序運行過程中遍歷繼承鏈,如果途中遇到了要轉換的目標類型,那么就能夠轉換成功,如果直到繼承鏈的頂點(最頂層的基類)還沒有遇到要轉換的目標類型,那么就轉換失敗。對于同一個指針(例如 pa),它指向的對象不同,會導致遍歷繼承鏈的起點不一樣,途中能夠匹配到的類型也不一樣,所以相同的類型轉換產生了不同的結果。從表面上看起來 dynamic_cast 確實能夠向下轉型,本例也很好地證明了這一點:B 和 C 都是 A 的派生類,我們成功地將 pa 從 A 類型指針轉換成了 B 和 C 類型指針。

但是從本質上講,dynamic_cast 還是只允許向上轉型,因為它只會向上遍歷繼承鏈。造成這種假象的根本原因在于,派生類對象可以用任何一個基類的指針指向它,這樣做始終是安全的。本例中的情況②,pa 指向的對象是 D 類型的,pa、pb、pc 都是 D 的基類的指針,所以它們都可以指向 D 類型的對象,dynamic_cast 只是讓不同的基類指針指向同一個派生類對象罷了。

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

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

相關文章

MySQL中如何知道數據庫表中所有表的字段的排序規則是什么?

查看所有表的字段及其排序規則&#xff1a; 你可以查詢 information_schema 數據庫中的 COLUMNS 表&#xff0c;來獲取所有表的字段及其排序規則。以下是一個示例查詢&#xff1a; SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, COLLATION_NAME FROM information_schema.COL…

【設計模式深度剖析】【5】【創建型】【原型模式】| 類比群發郵件,加深理解

&#x1f448;?上一篇:建造者模式 | 下一篇:創建型設計模式對比&#x1f449;? 目錄 原型模式(Prototype Pattern)概覽定義英文原話直譯 3個角色類圖1. 抽象原型&#xff08;Prototype&#xff09;角色2. 具體原型&#xff08;Concrete Prototype&#xff09;角色3. 客戶…

必示科技參與智能運維國家標準預研線下編寫會議并做主題分享

近日&#xff0c;《信息技術服務 智能運維 第3部分&#xff1a;算法治理》&#xff08;擬定名&#xff09;國家標準預研階段第一次編寫工作會議在杭州舉行。本次會議由浙商證券承辦。 此次編寫有來自銀行、證券、保險、通信、高校研究機構、互聯網以及技術方等29家單位&#xf…

在云計算環境中,如何實現資源的高效分配和調度?

在云計算環境中&#xff0c;可以通過以下幾種方法實現資源的高效分配和調度&#xff1a; 負載均衡&#xff1a;通過負載均衡算法&#xff0c;將云計算集群的負載均勻地分配到各個節點上。常見的負載均衡算法有輪詢、最小連接數、最短響應時間等。 資源調度算法&#xff1a;為了…

Linux基礎(四):Linux系統文件類型與文件權限

各位看官&#xff0c;好久不見&#xff0c;在正式介紹Linux的基本命令之前&#xff0c;我們首先了解一下&#xff0c;關于文件的知識。 目錄 一、文件類型 二、文件權限 2.1 文件訪問者的分類 2.2 文件權限 2.2.1 文件的基本權限 2.2.2 文件權限值的表示方法 三、修改文…

CSS3 新增背景屬性 + 新增邊框屬性(如果想知道CSS3新增背景屬性和新增邊框屬性的知識點,那么只看這一篇就夠了!)

前言&#xff1a;CSS3在CSS2的基礎上&#xff0c;新增了很多強大的新功能&#xff0c;從而解決一些實際面臨的問題&#xff0c;本篇文章主要講解的為CSS3新增背景屬性和新增邊框屬性。 ???這里是秋刀魚不做夢的BLOG ???想要了解更多內容可以訪問我的主頁秋刀魚不做夢-CSD…

視覺SLAM十四講:從理論到實踐(Chapter5:相機與圖像)

前言 學習筆記&#xff0c;僅供學習&#xff0c;不做商用&#xff0c;如有侵權&#xff0c;聯系我刪除即可 目標 理解針孔相機的模型、內參與徑向畸變參數。理解一個空間點是如何投影到相機成像平面的。掌握OpenCV的圖像存儲與表達方式。學會基本的攝像頭標定方法。 一、相…

機器學習第四十周周報 WDN GGNN

文章目錄 week40 WDN GGNN摘要Abstract一、文獻閱讀1. 題目2. abstract3. 網絡架構3.1 問題提出3.2 GNN3.3 CSI GGNN 4. 文獻解讀4.1 Introduction4.2 創新點4.3 實驗過程4.3.1 數據獲取4.3.2 參數設置4.3.3 實驗結果 5. 結論二、GGNN1. 代碼解釋2. 網絡結構小結參考文獻參考文…

Vue 2 和 Vue 3 中同步和異步

Vue 2 和 Vue 3 中同步和異步 Vue 2 同步和異步 同步更新 (Synchronous Updates) Vue 2 在數據更新后會進行同步渲染更新,但為了性能優化,Vue 會在內部隊列中異步地進行 DOM 更新。這意味著數據變化會立即被捕捉到,但實際的 DOM 更新會被推遲到下一個事件循環隊列中。new V…

基礎3 探索JAVA圖形編程桌面:邏輯圖形組件實現

在一個寬敞明亮的培訓教室里&#xff0c;陽光透過窗戶柔和地灑在地上&#xff0c;教室里擺放著整齊的桌椅。臥龍站在講臺上&#xff0c;面帶微笑&#xff0c;手里拿著激光筆&#xff0c;他的眼神中充滿了熱情和期待。他的聲音清晰而洪亮&#xff0c;傳遍了整個教室&#xff1a;…

Linux模擬考試

注意&#xff0c;以下答案僅供參考 1、某CentOS系統空間不夠&#xff0c;現加一塊100G的硬盤(是系統的第二塊硬盤&#xff09;&#xff0c;分為一個區99G&#xff0c;掛載點是/data&#xff0c;請寫出從分區到掛載并使用的整個步驟及相關命令。 1.創建分區&#xff1a; sudo f…

HTML5 通信方式及應用

目錄 postMessage APIWebSocketsServer-Sent Events (SSE)Fetch API / XMLHttpRequest (XHR)Web Workers & Service WorkersHTML5 提供了多種通信方式,使得瀏覽器中的Web應用能夠實現頁面間、窗口間、甚至與外部服務的有效通信。這些通信方式大大提升了Web應用的交互性和復…

Hsql每日一題 | day02

前言 就一直向前走吧&#xff0c;沿途的花終將綻放~ 題目&#xff1a;主播同時在線人數問題 如下為某直播平臺主播開播及關播時間&#xff0c;根據該數據計算出平臺最高峰同時在線的主播人數。 id stt edt 1001,2021-06-14 12:12:12,2021-06-14 18:1…

【錯誤解決】使用HuggingFaceInstructEmbeddings時的一個錯誤

起因&#xff1a;使用huggingface構建一個問答程序時出現的問題。 錯誤內容&#xff1a; 分析&#xff1a; 查看代碼發現&#xff0c;HuggingFaceInstructEmbeddings和sentence-transformers模塊版本不兼容導致。 可以明顯看到方法參數不同。 解決&#xff1a; 安裝sentenc…

json 讀寫 python

目錄 這里對json保存做了格式封裝 調用代碼&#xff1a; python json原始保存是所有json保存一行&#xff0c; 這里對json保存做了格式封裝 import numpy as np class MyEncoder(json.JSONEncoder):def default(self, obj):if isinstance(obj, np.integer):return int(obj)…

element-ui的Form 表單有些項的參數校驗

項目場景&#xff1a; 提示&#xff1a;項目相關背景&#xff1a; 項目場景&#xff1a;有時候自己的Form 表單中的某幾項引入的一些項不好去校驗 這樣的咋去校驗呢&#xff1f; 解決方案&#xff1a; 提示&#xff1a;問題的具體解決方案&#xff1a; 例如&#xff1a;寫一…

【pyspark速成專家】3_Spark之RDD編程1

目錄 ?編輯 一&#xff0c;創建RDD 二&#xff0c;常用Action操作 三&#xff0c;常用Transformation操作 一&#xff0c;創建RDD 創建RDD主要有兩種方式&#xff0c;一個是textFile加載本地或者集群文件系統中的數據&#xff0c; 第二個是用parallelize方法將Driver中的…

fortran77 初始化矩陣 打印矩陣 模版 備拷

1&#xff0c;源碼 SUBROUTINE INIT_MATRIX(A, m, n, lda)DOUBLE PRECISION A(*)CALL SRAND(2024)DO i1, mDO j1, nA(i lda*(j-1)) RAND() RAND() C WRITE(*, (F8.4)) A(i)END DOEND DOENDSUBROUTINE PRINT_MATRIX(A, m, n, lda)DOUBLE PREC…

解釋Python中的上下文管理器(context manager)

Python中的上下文管理器&#xff08;Context Manager&#xff09;是一種用于管理某些資源的對象&#xff0c;如文件、網絡連接、數據庫連接等。這些資源在使用完畢后需要進行清理操作&#xff0c;如關閉文件、斷開連接等。通過上下文管理器&#xff0c;Python提供了一種優雅的方…

【Vue3】封裝axios請求(cli和vite)

原文作者&#xff1a;我輩李想 版權聲明&#xff1a;文章原創&#xff0c;轉載時請務必加上原文超鏈接、作者信息和本聲明。 Vue 【Vue3】env環境變量的配置和使用&#xff08;區分cli和vite&#xff09; 文章目錄 Vue前言一、常見用法二、vue3cli封裝接口1..env配置2..dev(開…