C++強制類型轉(zhuǎn)換的四種方式
1 C++類型轉(zhuǎn)換本質(zhì)
1.1 自動類型轉(zhuǎn)換(隱式)
利用編譯器內(nèi)置的轉(zhuǎn)換規(guī)則,或者用戶自定義的轉(zhuǎn)換構(gòu)造函數(shù)以及類型轉(zhuǎn)換函數(shù)(這些都可以認為是已知的轉(zhuǎn)換規(guī)則)。
例如從 int 到 double、從派生類到基類、從type *到void *、從 double 到 Complex 等。
注:
type *是一個具體類型的指針,例如int *、double *、Student *等,它們都可以直接賦值給void *指針。
例如,malloc() 分配內(nèi)存后返回的就是一個void *指針,我們必須進行強制類型轉(zhuǎn)換后才能賦值給指針變量。
1.2 強制類型轉(zhuǎn)換(顯式)
隱式不能完成的類型轉(zhuǎn)換工作,就必須使用強制類型轉(zhuǎn)換
(new_type) expression
1.3 類型轉(zhuǎn)換的本質(zhì)
數(shù)據(jù)類型的本質(zhì):
這種「確定數(shù)據(jù)的解釋方式」的工作就是由數(shù)據(jù)類型(Data Type)來完成的。例如int a;表明,a 這份數(shù)據(jù)是整數(shù),不能理解為像素、聲音、視頻等。
數(shù)據(jù)類型轉(zhuǎn)換的本質(zhì):
數(shù)據(jù)類型轉(zhuǎn)換,就是對數(shù)據(jù)所占用的二進制位做出重新解釋。
- 隱式類型轉(zhuǎn)換:編譯器可以根據(jù)已知的轉(zhuǎn)換規(guī)則來決定是否需要修改數(shù)據(jù)的二進制位
- 強制類型轉(zhuǎn)換:由于沒有對應的轉(zhuǎn)換規(guī)則,所以能做的事情僅僅是重新解釋數(shù)據(jù)的二進制位,但無法對數(shù)據(jù)的二進制位做出修正。
1.4 類型轉(zhuǎn)換的安全性
隱式類型轉(zhuǎn)換必須使用已知的轉(zhuǎn)換規(guī)則,雖然靈活性受到了限制,但是由于能夠?qū)?shù)據(jù)進行恰當?shù)卣{(diào)整,所以更加安全(幾乎沒有風險)。
強制類型轉(zhuǎn)換能夠在更大范圍的數(shù)據(jù)類型之間進行轉(zhuǎn)換,例如不同類型指針(引用)之間的轉(zhuǎn)換、從 const 到非 const 的轉(zhuǎn)換、從 int 到指針的轉(zhuǎn)換(有些編譯器也允許反過來)等,這雖然增加了靈活性,但是由于不能恰當?shù)卣{(diào)整數(shù)據(jù),所以也充滿了風險,程序員要小心使用。
2 四種類型轉(zhuǎn)換運算符
2.1 C語言的強制類型轉(zhuǎn)換與C++的區(qū)別
C風格的強制類型轉(zhuǎn)換統(tǒng)一使用(),而()在代碼中隨處可見,所以也不利于使用檢索工具定位強轉(zhuǎn)的代碼位置。
C++ 對類型轉(zhuǎn)換進行了分類,并新增了四個關(guān)鍵字來予以支持,它們分別是:
關(guān)鍵字 說明
static_cast 用于良性轉(zhuǎn)換,一般不會導致意外發(fā)生,風險很低。
const_cast 用于 const 與非 const、volatile 與非 volatile 之間的轉(zhuǎn)換。
reinterpret_cast 高度危險的轉(zhuǎn)換,這種轉(zhuǎn)換僅僅是對二進制位的重新解釋,不會借助已有的轉(zhuǎn)換規(guī)則對數(shù)據(jù)進行調(diào)整,但是可以實現(xiàn)最靈活的 C++ 類型轉(zhuǎn)換。
dynamic_cast 借助 RTTI,用于類型安全的向下轉(zhuǎn)型(Downcasting)。
語法格式為: xxx_cast<newType>(data)
3 static_cast
static_cast 是“靜態(tài)轉(zhuǎn)換”的意思,也就是在編譯期間轉(zhuǎn)換,轉(zhuǎn)換失敗的話會拋出一個編譯錯誤。
舉個例子:
#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; } ?//類型轉(zhuǎn)換函數(shù) private: ? ? double m_real; ? ? double m_imag; }; int main(){ ? ? //下面是正確的用法 ? ? int m = 100; ? ? Complex c(12.5, 23.8); ? ? long n = static_cast<long>(m); ?//寬轉(zhuǎn)換,沒有信息丟失 ? ? char ch = static_cast<char>(m); ?//窄轉(zhuǎn)換,可能會丟失信息 ? ? int *p1 = static_cast<int*>( malloc(10 * sizeof(int)) ); ?//將void指針轉(zhuǎn)換為具體類型指針 ? ? void *p2 = static_cast<void*>(p1); ?//將具體類型指針,轉(zhuǎn)換為void指針 ? ? double real= static_cast<double>(c); ?//調(diào)用類型轉(zhuǎn)換函數(shù) ? ? ? ? //下面的用法是錯誤的 ? ? float *p3 = static_cast<float*>(p1); ?//不能在兩個具體類型的指針之間進行轉(zhuǎn)換 ? ? p3 = static_cast<float*>(0X2DF9); ?//不能將整數(shù)轉(zhuǎn)換為指針類型 ? ? return 0; }
4 reinterpret_cast
reinterpret_cast 用于進行各種不同類型的指針之間、不同類型的引用之間以及指針和能容納指針的整數(shù)類型之間的轉(zhuǎn)換。轉(zhuǎn)換時,執(zhí)行的是逐個比特復制的操作。
#include <iostream> using namespace std; class A { public: int i; int j; A(int n):i(n),j(n) { } }; int main() { A a(100); int &r = reinterpret_cast<int&>(a); //強行讓 r 引用 a r = 200; //把 a.i 變成了 200 cout << a.i << "," << a.j << endl; // 輸出 200,100 int n = 300; A *pa = reinterpret_cast<A*> ( & n); //強行讓 pa 指向 n pa->i = 400; // n 變成 400 pa->j = 500; //此條語句不安全,很可能導致程序崩潰 cout << n << endl; // 輸出 400 long long la = 0x12345678abcdLL; pa = reinterpret_cast<A*>(la); //la太長,只取低32位0x5678abcd拷貝給pa unsigned int u = reinterpret_cast<unsigned int>(pa);//pa逐個比特拷貝到u cout << hex << u << endl; //輸出 5678abcd typedef void (* PF1) (int); typedef int (* PF2) (int,char *); PF1 pf1; PF2 pf2; pf2 = reinterpret_cast<PF2>(pf1); //兩個不同類型的函數(shù)指針之間可以互相轉(zhuǎn)換 }
reinterpret_cast體現(xiàn)了 C++ 語言的設計思想:用戶可以做任何操作,但要為自己的行為負責。
5 const_cast
const_cast 運算符僅用于進行去除 const 屬性的轉(zhuǎn)換,它也是四個強制類型轉(zhuǎn)換運算符中唯一能夠去除 const 屬性的運算符。
將 const 引用轉(zhuǎn)換為同類型的非 const 引用,將 const 指針轉(zhuǎn)換為同類型的非 const 指針時可以使用 const_cast 運算符。例如:
const string s = "Inception"; string& p = const_cast <string&> (s); string* ps = const_cast <string*> (&s); // &s 的類型是 const string*
6 dynamic_cast
用 reinterpret_cast 可以將多態(tài)基類(包含虛函數(shù)的基類)的指針強制轉(zhuǎn)換為派生類的指針,但是這種轉(zhuǎn)換不檢查安全性,即不檢查轉(zhuǎn)換后的指針是否確實指向一個派生類對象。
dynamic_cast專門用于將多態(tài)基類的指針或引用強制轉(zhuǎn)換為派生類的指針或引用,而且能夠檢查轉(zhuǎn)換的安全性。對于不安全的指針轉(zhuǎn)換,轉(zhuǎn)換結(jié)果返回 NULL 指針。
dynamic_cast 用于在類的繼承層次之間進行類型轉(zhuǎn)換,它既允許向上轉(zhuǎn)型(Upcasting),也允許向下轉(zhuǎn)型(Downcasting)。向上轉(zhuǎn)型是無條件的,不會進行任何檢測,所以都能成功;向下轉(zhuǎn)型的前提必須是安全的,要借助 RTTI 進行檢測,所有只有一部分能成功。
dynamic_cast 與 static_cast 是相對的,dynamic_cast 是“動態(tài)轉(zhuǎn)換”的意思,static_cast 是“靜態(tài)轉(zhuǎn)換”的意思。dynamic_cast 會在程序運行期間借助 RTTI 進行類型轉(zhuǎn)換,這就要求基類必須包含虛函數(shù);static_cast 在編譯期間完成類型轉(zhuǎn)換,能夠更加及時地發(fā)現(xiàn)錯誤。
dynamic_cast 是通過“運行時類型檢查”來保證安全性的。
dynamic_cast 不能用于將非多態(tài)基類的指針或引用強制轉(zhuǎn)換為派生類的指針或引用——這種轉(zhuǎn)換沒法保證安全性,只好用 reinterpret_cast 來完成。
6.1 向上轉(zhuǎn)型(Upcasting)
向上轉(zhuǎn)型時,只要待轉(zhuǎn)換的兩個類型之間存在繼承關(guān)系,并且基類包含了虛函數(shù)(這些信息在編譯期間就能確定),就一定能轉(zhuǎn)換成功。因為向上轉(zhuǎn)型始終是安全的,所以 dynamic_cast 不會進行任何運行期間的檢查,這個時候的 dynamic_cast 和 static_cast 就沒有什么區(qū)別了。
「向上轉(zhuǎn)型時不執(zhí)行運行期檢測」雖然提高了效率,但也留下了安全隱患,請看下面的代碼:
#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(); ?//內(nèi)存錯誤 ? ? return 0; }
情況①是正確的,沒有任何問題。對于情況②,pd 指向的是整型變量 n,并沒有指向一個 Derived 類的對象,在使用 dynamic_cast 進行類型轉(zhuǎn)換時也沒有檢查這一點,而是將 pd 的值直接賦給了 pb(這里并不需要調(diào)整偏移量),最終導致 pb 也指向了 n。因為 pb 指向的不是一個對象,所以get_a()得不到 m_a 的值(實際上得到的是一個垃圾值),pb2->func()也得不到 func() 函數(shù)的正確地址。
pb2->func()得不到 func() 的正確地址的原因在于,pb2 指向的是一個假的“對象”,它沒有虛函數(shù)表,也沒有虛函數(shù)表指針,而 func() 是虛函數(shù),必須到虛函數(shù)表中才能找到它的地址。
6.2 向下轉(zhuǎn)型(Downcasting)
向下轉(zhuǎn)型是有風險的,dynamic_cast 會借助 RTTI 信息進行檢測,確定安全的才能轉(zhuǎn)換成功,否則就轉(zhuǎn)換失敗。那么,哪些向下轉(zhuǎn)型是安全地呢,哪些又是不安全的呢?下面我們通過一個例子來演示:
#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); ?//向下轉(zhuǎn)型失敗 ? ? 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); ?//向下轉(zhuǎn)型失敗 ? ? 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(); ?//向上轉(zhuǎn)型都是允許的 ? ? pb = dynamic_cast<B*>(pa); ?//向下轉(zhuǎn)型成功 ? ? 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); ?//向下轉(zhuǎn)型成功 ? ? if(pc == NULL){ ? ? ? ? cout<<"Downcasting failed: A* to C*"<<endl; ? ? }else{ ? ? ? ? cout<<"Downcasting successfully: A* to C*"<<endl; ? ? ? ? pc -> func(); ? ? } ? ? ? ? return 0; }
當使用 dynamic_cast 對指針進行類型轉(zhuǎn)換時,程序會先找到該指針指向的對象,再根據(jù)對象找到當前類(指針指向的對象所屬的類)的類型信息,并從此節(jié)點開始沿著繼承鏈向上遍歷,如果找到了要轉(zhuǎn)化的目標類型,那么說明這種轉(zhuǎn)換是安全的,就能夠轉(zhuǎn)換成功,如果沒有找到要轉(zhuǎn)換的目標類型,那么說明這種轉(zhuǎn)換存在較大的風險,就不能轉(zhuǎn)換。
對于本例中的情況①,pa 指向 A 類對象,根據(jù)該對象找到的就是 A 的類型信息,當程序從這個節(jié)點開始向上遍歷時,發(fā)現(xiàn) A 的上方?jīng)]有要轉(zhuǎn)換的 B 類型或 C 類型(實際上 A 的上方?jīng)]有任何類型了),所以就轉(zhuǎn)換敗了。對于情況②,pa 指向 D 類對象,根據(jù)該對象找到的就是 D 的類型信息,程序從這個節(jié)點向上遍歷的過程中,發(fā)現(xiàn)了 C 類型和 B 類型,所以就轉(zhuǎn)換成功了。
總起來說,dynamic_cast 會在程序運行過程中遍歷繼承鏈,如果途中遇到了要轉(zhuǎn)換的目標類型,那么就能夠轉(zhuǎn)換成功,如果直到繼承鏈的頂點(最頂層的基類)還沒有遇到要轉(zhuǎn)換的目標類型,那么就轉(zhuǎn)換失敗。對于同一個指針(例如 pa),它指向的對象不同,會導致遍歷繼承鏈的起點不一樣,途中能夠匹配到的類型也不一樣,所以相同的類型轉(zhuǎn)換產(chǎn)生了不同的結(jié)果。
從表面上看起來 dynamic_cast 確實能夠向下轉(zhuǎn)型,本例也很好地證明了這一點:B 和 C 都是 A 的派生類,我們成功地將 pa 從 A 類型指針轉(zhuǎn)換成了 B 和 C 類型指針。但是從本質(zhì)上講,dynamic_cast 還是只允許向上轉(zhuǎn)型,因為它只會向上遍歷繼承鏈。造成這種假象的根本原因在于,派生類對象可以用任何一個基類的指針指向它,這樣做始終是安全的。本例中的情況②,pa 指向的對象是 D 類型的,pa、pb、pc 都是 D 的基類的指針,所以它們都可以指向 D 類型的對象,dynamic_cast 只是讓不同的基類指針指向同一個派生類對象罷了。
參考:http://c.biancheng.net/view/2343.html
到此這篇關(guān)于C++強制類型轉(zhuǎn)換的四種方式的文章就介紹到這了,更多相關(guān)C++強制類型轉(zhuǎn)換內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++ float轉(zhuǎn)std::string 小數(shù)位數(shù)控制問題
這篇文章主要介紹了C++ float轉(zhuǎn)std::string 小數(shù)位數(shù)控制問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11c++調(diào)用實現(xiàn)yolov5轉(zhuǎn)onnx介紹
大家好,本篇文章主要講的是c++調(diào)用實現(xiàn)yolov5轉(zhuǎn)onnx介紹,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12