誰說C++ 的強制類型轉換很難懂?

2020-12-11 CSDN

作者 | 櫻雨樓

責編 | 屠敏

在上篇與中篇中,我們討論了隱式類型轉換及其與函數重載之間的相關話題。本篇將要討論的即為類型轉換的另一大分支——強制類型轉換。

C風格的強制類型轉換

在C語言中,強制類型轉換存在兩種等價形式:Type(Value)或(Type)Value。

參考以下代碼:

intmain(){ (int *) malloc(0); // (Type)Value形式的強制類型轉換int(0.); // Type(Value)形式的強制類型轉換}

上述代碼中,我們分別使用了C語言的提供的兩種強制類型轉換的等價形式將void *轉為了int *,以及將double轉為了int。

static_cast

在C++中,staticcast相當於C語言中的強制類型轉換語法。staticcast用於在編譯期對某種類型的變量進行強制類型轉換。

參考以下代碼:

intmain(){static_cast<int *>(malloc(0));static_cast<int>(0.);}

上述代碼中,我們使用了static_cast分別將void *轉為了int *,以及將double轉為了int。

const_cast

constcast是C++中專用於處理與const相關的強制類型轉換關鍵字,其功能為:為一個變量重新設定其const描述。即:constcast可以為一個變量強行增加或刪除其const限定。

需要明確的是,即使用戶通過constcast強行去除了const屬性,也不代表當前變量從不可變變為了可變。constcast只是使得用戶接管了編譯器對於const限定的管理權,故用戶必須遵守「不修改變量」的承諾。如果違反此承諾,編譯器也不會因此而引發編譯時錯誤,但可能引發運行時錯誤。

下面討論const_cast的主要用途。

考察以下代碼:

structA {A &test(){ return *this; } };intmain(){ A().test();}

這段代碼看上去運行正常。但如果:

structA {A &test(){ return *this; } };intmain(){const A a; a.test(); // Error!}

我們試圖用一個const對象去調用非const成員函數,此時,為了調用此成員函數,const A *this就需要轉換為A *this,這顯然是不行的。

經過上述討論,我們可以將代碼修改為如下:

structA {const A &test()const{ return *this; } };intmain(){const A a; a.test();}

我們將this指針聲明為const A *,解決了此問題。但不難發現,如果我們通過一個非const對象調用此方法,其返回值也會被轉為const,從而不再可以繼續調用任何接受A *this的成員函數。這明顯不是我們想要的結果:

structA{const A &test()const{ return *this; }A &test2(){ return *this; }};intmain(){ A().test().test2(); // Error!}

怎麼解決此問題呢?根據C++函數重載的規則,我們可以為test成員函數同時定義const與非const版本:

structA{A &test(){ return *this; }const A &test()const{ return *this; }A &test2(){ return *this; }};intmain(){ A().test().test2();const A a; a.test();}

對於A的非const實例而言,test的非const版本是精確匹配,故編譯器將選擇此版本,從而返回一個A &;同時,對於A的const實例而言,const版本的test是其唯一可用的版本,返回一個const A &。

至此,問題解決了。我們基於const的有無重載出了兩個版本的成員函數,從而使得const對象與非const對象能夠各自調用不同的版本,互不影響。

在實際情況中,我們定義的兩個版本的重載函數除了有無const以外往往沒有任何區別,此時就可以使用const_cast定義第二個重載版本,而無需寫兩遍一模一樣的函數體。

參考以下代碼:

structA{A &test(){ ... }// 通過const_cast強行去除this的const限定後調用非const版本// 返回值通過隱式類型轉換再轉回const A &const A &test()const{ returnconst_cast<A *>(this)->test(); }};

上述代碼中,我們首先定義了一個非const版本的test成員函數,這個成員函數將提供給A *this調用;在定義test成員函數的const版本時,我們通過const_cast\<A *>(this),將此版本的const A *this指針轉換為非const版本需要的A *this類型指針,然後調用了非const版本的test成員函數,並返回其調用結果,非const版本的test成員函數的返回值將通過隱式類型轉換轉為const A &。

由此可見,通過const_cast,我們僅需一行代碼就可以完成第二個函數重載版本的定義。

dynamic_cast

上文提到,動態類型為繼承類的指針或引用可以存儲在靜態類型為基類的變量中,且不會發生隱式類型轉換。對於一個變量而言,雖然其動態類型確實是繼承類,但由於編譯期與運行期的差別,其也無法跨越「可使用的成員名稱由靜態類型決定」這一規則。

雖然繼承類可以通過虛函數的方式一定程度上解決此種情況,但是,如果某個成員函數不是基類虛函數,而只存在於繼承類中呢?dynamic_cast為我們提供了解決方案。

當一個靜態類型為基類指針或引用的變量確實存放了繼承類指針或引用時,從基類向繼承類的類型轉換,即向下類型轉換理論上是可行的,dynamic_cast即用於在運行時實現向下類型轉換。

需要注意的是,dynamic_cast的使用必須同時滿足以下所有條件:

被轉換的變量的類型為基類指針或引用,且其確實存放了一個繼承類指針或引用基類具有虛表,即基類必須至少定義了一個虛函數參考以下代碼:

structA {virtualvoidtest(){} }; // 基類含有虛函數structB: A { voidtest2(){} }; // 繼承類特有函數intmain(){// 靜態類型為基類指針的變量存放繼承類指針 A *b = new B;// 通過向下類型轉換調用繼承類特有函數dynamic_cast<B *>(b)->test2();}

上述代碼中,我們首先定義了具有虛函數的基類A,然後定義了具有繼承類特有函數的類B。此時,由於test2成員函數並未在基類中註冊為虛函數,我們將無法通過靜態類型為A *的變量b調用此函數。但由於我們可以確定變量b的動態類型為B *,則可以於運行時通過dynamic_cast將變量b的靜態類型轉為B *,然後調用繼承類的特有函數test2。

reinterpret_cast

reinterpret,即「重新解釋」,顧名思義,這個強制類型轉換的作用是提供某個變量在底層數據上的重新解釋。當我們對一個變量使用reinterpretcast後,編譯器將無視任何不合理行為,強行將被轉換變量的內存數據重解釋為某個新的類型。需要注意的是,reinterpretcast要求轉換前後的類型所佔用內存大小一致,否則將引發編譯時錯誤。

參考以下代碼:

intmain(){reinterpret_cast<int *>(0); // 強行將一個整數的內存數據解釋為一個int *}

討論

程式語言的強類型與弱類型相關話題,多年來業界一直討論不休,有的語言發展出了高度弱類型的語法體系,而有的語言則相對嚴謹,要求用戶儘可能多的使用顯式類型轉換。C++作為一門經典的弱類型語言,其類型轉換的相關話題自然十分龐大。

縱觀C++的類型轉換語法體系,其延續了C++一貫的包羅萬象風格,不僅為用戶提供了自定義類型轉換的極大自由度,也在語法層面為類型轉換可能會帶來的各種錯綜複雜的情況作出了嚴謹的規定。

保守看來,如果對C++的類型轉換沒有深入的理解,或不希望大量使用隱式類型轉換時,我們不應過度的依賴諸如非explicit轉換構造函數,自定義的類型轉換操作符,以及涉及隱式類型轉換的各種重載確定等語法組分。但作為C++語法體系的一個重要部分,深入理解C++關於類型轉換的各種話題,必定是十分重要的。

作者簡介:櫻雨樓,畢業於生物信息學專業,是一枚Python/C++/Perl開發,自稱R語言黑粉,GitHub勾搭:https://github.com/yingyulou

相關焦點

  • c++之內存分配、命名空間、強制類型轉換學習總結
    +中的四種強制類型轉換強制類型轉換類型匯總static_castconst_castdynamic_castreinterpret_cast用法:xxx_cast(Expression)下面是每種強制類型的具體講解:1、static_cast強制類型轉換:代碼解析:#include <stdio.h>void
  • 4 種 C++ 強制類型轉換,你都清楚嗎?
    (給CPP開發者加星標,提升C/C++技能)我們先來回憶以下,C 語言的強制類型轉換形式:這種舊式強制類型轉換從表現形式上來說不夠清晰明了
  • C++的轉換手段並與explicit關鍵詞配合使用
    顯示轉化C風格的強制轉換(Type Cast)容易理解,不管什麼類型的轉換都可以使用使用下面的方式Type b = (Type)a;當然,C++也是支持C風格的強制轉換,但是C風格的強制轉換可能帶來一些隱患
  • java數據類型的強制轉換及相關注意事項
    各位小夥伴們大家好,在上一篇文章中,小編介紹的是數據類型的自動轉換,這次小編要介紹的是數據類型的強制轉換。之前小編有講過,如果數據要進行自動類型轉換,就要符合從小到大的規則,如果不符合從小到大的規則,就無法進行自動類型轉換,如果這個時候就需要用到數據類型的強制轉換。
  • MSSQL類型轉換函數-強制類型轉換(STR、CONVERT、CAST、PARSE)
    轉換函數將值從一種數據類型轉換為另一種。在資料庫中很多時候需要把一個類型的數據轉換成另一個數據類型來進行運算或者輸出顯示,這種轉換有的時候可以隱士的轉換有的時候就需要強制類型轉換。在保證數據正確的情況下進行強制類型轉換。
  • 釐清C++類型轉換
    1、舊式類型轉換開門見山,先聊聊筆者對類型轉換的看法吧。從設計上看,一門面向對象的語言是不一樣提供類型轉換的,這種方式破壞了類型系統。C++為了兼容C也不得不吞下這個苦果,在實際進行編程工作的過程之中,並不太推薦大家使用類型轉換。(Java在這裡就做了一些妥協,在基本類型之中提供了類型轉換。
  • C/C++中字符串與數字轉換
    ,方法一和方法二是c++中的方法,方法三和方法四是C語言庫函數的方法。方法一:c++11中string中添加了下面這些方法幫助完成字符串和數字的相互轉換stod stof stoi stol stold stoll stoul stoull函數原型:float stof (const string& str, size_t* idx = 0);to_string to_wstring
  • 詳解C++ 的隱式類型轉換與函數重載!
    >涉及底層const的類型轉換被視為最接近精確匹配的隱式類型轉換類型提升優先於算術類型轉換自定義的類型轉換等級最低,且各種自定義類型轉換之間無先後差別我們首先來看一個最簡單的例子:voidtest(int
  • java基礎案例之java語言組成和數據類型與強制類型轉換語法
    java數據類型定義變量格式:數據類型 變量名 =初始化值;例如:int x =4;java擁有不同類型運算時,會自動提升上一級類型。如:byte b=3;b=b+2; //前者b是byte類型,後者2是int類型。
  • c++ 之布爾類型和引用的學習總結!
    一、布爾數據類型:在c語言裡面我們知道是沒有布爾數據類型的,而在C++中添加了布爾數據類型(bool),它的取值是:true或者false(也就是1或者0),在內存大小上它佔用一個字節大小:1、bool類型只有true(非0)和false(0)兩個值。
  • 【C++】數據類型
    今天來講講不c++中同數據類型的使用方法和區別1.基礎類型類型範圍內存中佔的大小char-128~127或0~2551byteunsigned
  • C/C++編程筆記:C語言中的類型轉換,今天教你怎麼寫!
    所謂類型轉換基本上是從一種類型到另一種類型的轉換。類型轉換有兩種類型: 一、隱式類型轉換 通常在表達式中存在多個數據類型時發生。在這種情況下,將進行類型轉換(類型提升)以避免數據丟失。 變量的所有數據類型將升級為具有最大數據類型的變量的數據類型。
  • JavaScript數據類型轉換
    雖然變量的數據類型是不確定的,但是各種運算符對數據類型是有要求的。如果運算符發現,運算子的類型與預期不符,就會自動轉換類型。本文主要介紹數據類型強制轉換和自動轉換,自動轉換是基於強制轉換之上。強制轉換主要指使用Number、String和Boolean三個函數,手動將各種類型的值,分布轉換成數字、字符串或者布爾值。
  • python+C、C++混合編程的應用
    python與C/C++混合編程的本質是python調用C/C++編譯的動態連結庫,關鍵就是把python中的數據類型轉換成c/c++中的數據類型,給編譯函數處理,然後返回參數再轉換成python中的數據類型。
  • 程式設計師必備知識 || Java數據類型轉換中「顯式類型轉換」技巧整理?
    ,必須使用顯式類型轉換運算(又稱為強制類型轉換)。語法如下:(類型名)要轉換的值下面通過幾種常見的顯式數據類型轉換實例來說明。例如:將不同的數據類型進行顯式類型轉換,實例代碼如下:int a = (int)45.23; //此時輸出a的值為45
  • java數據類型的轉換之自動轉換
    各位小夥伴們大家好,這次,小編要介紹的是數據類型的轉換,之前小編有提到過一些基本的數據類型。什麼是數據類型轉換呢?就是數據類型不一樣的時候,將會發生一些數據類型的轉換,比如可以將int型轉換成long型。
  • 「Java」基礎08:數據類型的轉換
    二、強制轉換前面我們將一個int類型的數據賦值給一個double類型,它會完成自動轉換。那反過來會怎麼樣?1.2默認是double數據類型,當把一個double數據類型的數據賦值給一個int類型時,編譯會報錯。
  • 記住這個,能少走彎路,C++兩種隱式類型轉換
    本文將要講解的C++兩種隱式類型轉換,如果不了解其用法,那麼遇到問題的時候,可能就會陷入困境。所以接下來就來講解operator與構造函數的隱式類型轉換,首先通過例子說明operator的隱式類型轉換,再說明構造函數的隱式類型轉換,最後再總結它們之間的區別。
  • C++之字符串類學習總結
    ,所以更加無法獲得字符串類型為了解決這個問題,在c++中,引入了自定義類型,而且可以通過類來完成對字符串類型的定義。那麼C++中的原生類型系統是否包含字符串類型呢?答案是c++中並沒有提供原生的字符串類型。
  • 標準C+的類型轉換符:static_cast等
    一、 強制轉型 1) C 風格(C-style)強制轉型: (T) exdivssion // cast exdivssion to be of type T 2) 函數風格(Function-style)強制轉型使用這樣的語法