日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

顯示類型轉換 const_cast, static_cast, dynamic_cast, teinterpret_cast的使用

作者:四庫全書的酷 更新時間: 2022-05-13 編程語言

文章目錄

  • const_cast :: 對const和volatile限定符進行擦除
  • static_cast ::常用隱式轉型的顯示使用
  • dynamic_cast ::檢查安全向下轉型(繼承關系)
  • reinterpret_cast::跨越無關類型的轉換
        • reinterpret_cast有何作用
        • 當reinterpret_cast面對const

const_cast :: 對const和volatile限定符進行擦除

  • 主要作用
    constvolatile的限定作用擦除(沒有真的去除這個變量的限定類型,而實使用另一個變量去承接一個沒有這個限定類型的變量)。

舉例:

#include "head.h"

using namespace std;

int main(){
    const int a = 4;

    //  int &b1 = a;  
    //報錯:
    //binding reference of type ‘int&’ to ‘const int’ discards qualifiers

    //  int *p1 = &a;
    //報錯;
    //invalid conversion from ‘const int*’ to ‘int*’ [-fpermissive]


    int &b = const_cast<int&>(a);
    int *p = const_cast<int*>(&a);
    b = 3;
    return 0;
}

通過const_cast運算符,也只能將const type轉換為type,將const type&轉換為type&。

也就是說源類型和目標類型除了const屬性不同,其他地方完全相同。

static_cast ::常用隱式轉型的顯示使用

  • 編譯器隱式執行的任何類型轉換都可以由static_cast來完成,比如int與float、double與char、enum與int之間的轉換等
double a = 1.999;
int b = static_cast<double>(a); //相當于a = b ;

當編譯器隱式執行類型轉換時,大多數的編譯器都會給出一個警告:

e:\vs 2010 projects\static_cast\static_cast\static_cast.cpp(11):
warning C4244: “初始化”: 從“double”轉換到“int”,可能丟失數據

使用static_cast可以明確告訴編譯器,這種損失精度的轉換是在知情的情況下進行的,也可以讓閱讀程序的其他程序員明確你轉換的目的而不是由于疏忽。

把精度大的類型轉換為精度小的類型,static_cast使用位截斷進行處理。

  • 使用static_cast可以找回存放在void指針中的值*。
double a = 1.999;
void * vptr = & a;
double * dptr = static_cast<double*>(vptr);
cout<<*dptr<<endl;//輸出1.999
  • static_cast也可以用在于基類與派生類指針或引用類型之間的轉換。

然而它不做運行時的檢查,不如dynamic_cast安全。static_cast僅僅是依靠類型轉換語句中提供的信息來進行轉換,而dynamic_cast則會遍歷整個類繼承體系進行類型檢查,因此dynamic_cast在執行效率上比static_cast要差一些。現在我們有父類與其派生類如下:

class ANIMAL
{
public:
    ANIMAL():_type("ANIMAL"){};
    virtual void OutPutname(){cout<<"ANIMAL";};
private:
    string _type ;
};
class DOG:public ANIMAL
{
public:
    DOG():_name("大黃"),_type("DOG"){};
    void OutPutname(){cout<<_name;};
    void OutPuttype(){cout<<_type;};
private:
    string _name ;
    string _type ;
};

此時我們進行派生類與基類類型指針的轉換:注意從下向上的轉換是安全的,從上向下的轉換不一定安全。

int main()
{
    //基類指針轉為派生類指針,且該基類指針指向基類對象。
    ANIMAL * ani1 = new ANIMAL ;
    DOG * dog1 = static_cast<DOG*>(ani1);
    //dog1->OutPuttype();//錯誤,在ANIMAL類型指針不能調用方法OutPutType();在運行時出現錯誤。

    //基類指針轉為派生類指針,且該基類指針指向派生類對象
    ANIMAL * ani3 = new DOG;
    DOG* dog3 = static_cast<DOG*>(ani3);
    dog3->OutPutname(); //正確

    //子類指針轉為派生類指針
    DOG *dog2= new DOG;
    ANIMAL *ani2 = static_cast<DOG*>(dog2);
    ani2->OutPutname(); //正確,結果輸出為大黃

    //
    system("pause");

}
  • static_cast可以把任何類型的表達式轉換成void類型。

另外,與const_cast相比,static_cast不能轉換掉變量的const屬性,也包括volitale或者__unaligned屬性。

dynamic_cast ::檢查安全向下轉型(繼承關系)

dynamic_cast是四個強制類型轉換操作符中最特殊的一個,它支持運行時識別指針或引用。

首先,dynamic_cast依賴于RTTI信息,其次,在轉換時,dynamic_cast會檢查轉換的source對象是否真的可以轉換成target類型,

這種檢查不是語法上的,而是真實情況的檢查。

dynamic_cast主要用于“安全地向下轉型”
dynamic_cast用于類繼承層次間的指針或引用轉換。主要還是用于執行“安全的向下轉型(safe downcasting)”,
也即是基類對象的指針或引用轉換為同一繼承層次的其他指針或引用。

至于“先上轉型”(即派生類指針或引用類型轉換為其基類類型),本身就是安全的,盡管可以使用dynamic_cast進行轉換,但這是沒必要的, 普通的轉換已經可以達到目的,畢竟使用dynamic_cast是需要開銷的。

總結就是一句話:

向上轉型不需要dynamic_cast的檢查,他一定是安全的;
向下轉型的話,
如果一個指向子類的基類指針轉向該子類,是安全的;
如果一個指向基類的基類指針轉向某一子類,一定是不安全的,
如果一個指向子類的基類指針轉向其他子類,是不安全的;

 1 class Base
 2 {
 3 public:
 4     Base(){};
 5     virtual void Show(){cout<<"This is Base calss";}
 6 };
 7 class Derived:public Base
 8 {
 9 public:
10     Derived(){};
11     void Show(){cout<<"This is Derived class";}
12 };
13 int main()
14 {    
15     Base *base ;
16     Derived *der = new Derived;
17     //base = dynamic_cast(der); //正確,但不必要。
18     base = der; //先上轉換總是安全的
19     base->Show(); 
20     system("pause");
21 }
  • dynamic_cast與繼承層次的指針

對于“向下轉型”有兩種情況。

  1. 一種是基類指針所指對象是派生類類型的,這種轉換是安全的;

  2. 另一種是基類指針所指對象為基類類型,在這種情況下dynamic_cast在運行時做檢查,轉換失敗,返回結果為0;

#include "stdafx.h"
#include
using namespace std;

class Base
{
public:
    Base(){};
    virtual void Show(){cout<<"This is Base calss";}
};
class Derived:public Base
{
public:
    Derived(){};
    void Show(){cout<<"This is Derived class";}
};
int main()
{    
    //這是第一種情況
    Base* base = new Derived;
    if(Derived *der= dynamic_cast<Derived*>(base))
    {
        cout<<"第一種情況轉換成功"<<endl;
        der->Show();
        cout<<endl;
    }
    //這是第二種情況
    Base * base1 = new Base;
    if(Derived *der1 = dynamic_cast<Derived*>(base1))
    {
        cout<<"第二種情況轉換成功"<<endl;
        der1->Show();
    }
    else 
    {
        cout<<"第二種情況轉換失敗"<<endl;
    }

    delete(base);
    delete(base1);
    system("pause");
}

運行結果:
在這里插入圖片描述

  • dynamic_cast和引用類型

在前面的例子中,使用了dynamic_cast將基類指針轉換為派生類指針,也可以使用dynamic_cast將基類引用轉換為派生類引用。

同樣的,引用的向上轉換總是安全的:

Derived c;
Derived & der2= c;
Base & base2= dynamic_cast<Base&>(der2);//向上轉換,安全
base2.Show();

所以,在引用上,dynamic_cast依舊是常用于“安全的向下轉型”。與指針一樣,引用的向下轉型也可以分為兩種情況,與指針不同的是,并不存在空引用,所以引用的dynamic_cast檢測失敗時會拋出一個bad_cast異常:

int main()
{    
    //第一種情況,轉換成功
    Derived b ;
    Base &base1= b;
    Derived &der1 = dynamic_cast<Derived&>(base1);
    cout<<"第一種情況:";
    der1.Show();
    cout<<endl;

    //第二種情況
    Base a ;
    Base &base = a ;
    cout<<"第二種情況:";
    try{
        Derived & der = dynamic_cast<Derived&>(base);
    }
    catch(bad_cast)
    {
        cout<<"轉化失敗,拋出bad_cast異常"<<endl;
    }
    system("pause");
}

運行結果:

在這里插入圖片描述

  • 使用dynamic_cast轉換的Base類至少帶有一個虛函數
    當一個類中擁有至少一個虛函數的時候,編譯器會為該類構建出一個虛函數表(virtual method table),虛函數表記錄了虛函數的地址。如果該類派生了其他子類,且子類定義并實現了基類的虛函數,那么虛函數表會將該函數指向新的地址。虛表是C++多態實現的一個重要手段,也是dynamic_cast操作符轉換能夠進行的前提條件。當類沒有虛函數表的時候(也即一個虛函數都沒有定義),dynamic_cast無法使用RTTI,不能通過編譯(個人猜想…有待驗證)。

reinterpret_cast::跨越無關類型的轉換

reinterpret_cast運算符是用來處理無關類型之間的轉換;它會產生一個新的值,這個值會有與原始參數(expressoin)有完全相同的比特位。

什么是無關類型?我沒有弄清楚,沒有找到好的文檔來說明類型之間到底都有些什么關系(除了類的繼承以外)。后半句倒是看出了reinterpret_cast的字面意思:重新解釋(類型的比特位)。我們真的可以隨意將一個類型值的比特位交給另一個類型作為它的值嗎?其實不然。

IBM的C++指南里倒是明確告訴了我們reinterpret_cast可以,或者說應該在什么地方用來作為轉換運算符:

從指針類型到一個足夠大的整數類型
從整數類型或者枚舉類型到指針類型
從一個指向函數的指針到另一個不同類型的指向函數的指針
從一個指向對象的指針到另一個不同類型的指向對象的指針
從一個指向類函數成員的指針到另一個指向不同類型的函數成員的指針
從一個指向類數據成員的指針到另一個指向不同類型的數據成員的指針

不過我在Xcode中測試了一下,事實上reinterpret_cast的使用并不局限在上邊所說的幾項的,任何類型的指針之間都可以互相轉換,都不會得到編譯錯誤。上述列出的幾項,可能 是Linux下reinterpret_cast使用的限制,也可能是IBM推薦我們使用reinterpret_cast的方式

所以總結來說:reinterpret_cast用在任意指針(或引用)類型之間的轉換;以及指針與足夠大的整數類型之間的轉換;從整數類型(包括枚舉類型)到指針類型,無視大小。

(所謂"足夠大的整數類型",取決于操作系統的參數,如果是32位的操作系統,就需要整形(int)以上的;如果是64位的操作系統,則至少需要長整形(long)。具體大小可以通過sizeof運算符來查看)。

reinterpret_cast有何作用

從上邊對reinterpret_cast介紹,可以感覺出reinterpret_cast是個很強大的運算符,因為它可以無視種族隔離,隨便搞。但就像生物的準則,不符合自然規律的隨意雜交只會得到不能長久生存的物種。隨意在不同類型之間使用reinterpret_cast,也之后造成程序的破壞和不能使用

比如下邊的代碼

typedef int (*FunctionPointer)(int);
int value = 21;
FunctionPointer funcP;
funcP = reinterpret_cast<FunctionPointer> (&value);
funcP(value);

我先用typedef定義了一個指向函數的指針類型,所指向的函數接受一個int類型作為參數。然后我用reinterpret_cast將一個整型的地址轉換成該函數類型并賦值給了相應的變量。最后,我還用該整形變量作為參數交給了指向函數的指針變量。

這個過程編譯器都成功的編譯通過,不過一旦運行我們就會得到"EXC_BAD_ACCESS"的運行錯誤,因為我們通過funcP所指的地址找到的并不是函數入口。

由此可知,reinterpret_cast雖然看似強大,作用卻沒有那么廣。IBM的C++指南、C++之父Bjarne Stroustrup的FAQ網頁和MSDN的Visual C++也都指出:

錯誤的使用reinterpret_cast很容易導致程序的不安全,只有將轉換后的類型值轉換回到其原始類型,這樣才是正確使用reinterpret_cast方式。

這樣說起來,reinterpret_cast轉換成其它類型的目的只是臨時的隱藏自己的什么(做個臥底?),要真想使用那個值,還是需要讓其露出真面目才行。那到底它在C++中有其何存在的價值呢?

MSDN的Visual C++ Developer Center 給出了它的使用價值:用來輔助哈希函數。下邊是MSNDN上的例子:

// expre_reinterpret_cast_Operator.cpp
// compile with: /EHsc
#include 
// Returns a hash code based on an address
unsigned short Hash( void *p ) {
	unsigned int val = reinterpret_cast<unsigned int>( p );
	return ( unsigned short )( val ^ (val >> 16));
}

using namespace std;
int main() {
	int a[20];
	for ( int i = 0; i < 20; i++ )
		cout << Hash( a + i ) << endl;
}

//如果跟我一樣是64位的系統,可能需要將unsigned int改成 unsigned long才能運行。

            

這段代碼適合體現哈希的思想,暫時不做深究,但至少看Hash函數里面的操作,也能體會到,對整數的操作顯然要對地址操作更方便。在集合中存放整形數值,也要比存放地址更具有擴展性(當然如果存void *擴展性也是一樣很高的),唯一損失的可能就是存取的時候整形和地址的轉換(這完全可以忽略不計)。

不過可讀性可能就不高,所以在這種情況下使用的時候,就可以用typedef來定義個指針類型:
typedef unsigned int PointerType;

這樣不是更棒,當我們在64位機器上運行的時候,只要改成:
typedef unsigned long PointerType;

當reinterpret_cast面對const

IBM的C++指南指出:reinterpret_cast不能像const_cast那樣去除const修飾符。 這是什么意思呢?代碼還是最直觀的表述:

int main() 
{
	typedef void (*FunctionPointer)(int);
	int value = 21;
	const int* pointer = &value;
	
	//int * pointer_r = reinterpret_cast (pointer); 
	// Error: reinterpret_cast from type 'const int*' to type 'int*' casts away constness
	
	FunctionPointer funcP = reinterpret_cast<FunctionPointer> (pointer);
}

例子里,我們像前面const_cast一篇舉到的例子那樣,希望將指向const的指針用運算符轉換成非指向const的指針。但是當實用reinterpret_cast的時候,編譯器直接報錯組織了該過程。這就體現出了const_cast的獨特之處。

但是,例子中還有一個轉換是將指向const int的指針付給指向函數的指針,編譯順利通過編譯,當然結果也會跟前面的例子一樣是無意義的。

如果我們換一種角度來看,這似乎也是合理的。因為

const int* p = &value;
int * const q = &value;

這兩個語句的含義是不同的,前者是"所指內容不可變",后者則是"指向的地址不可變"(具體參考此處)。因此指向函數的指針默認應該就帶有"所指內容不可變"的特性。

畢竟函數在編譯之后,其操作過程就固定在那里了,我們唯一能做的就是傳遞一些參數給指針,而無法改變已編譯函數的過程。所以從這個角度來想,上邊例子使用reinterpret_cast從const int * 到FunctionPointer轉換就變得合理了,因為它并沒有去除const限定

原文鏈接:https://blog.csdn.net/weixin_46535567/article/details/124630305

欄目分類
最近更新