本文討論static_cast<> 和 reinterpret_cast<>。
介紹
大多程序員在學C++前都學過C,並且習慣於C風格(類型)轉換。當寫C++(程序)時,有時候我們在使用static_cast<>和reinterpret_cast<>時可能會有點模糊。在本文中,我將說明static_cast<>實際上做了什麼,並且指出一些將會導致錯誤的情況。
泛型(Generic Types)
01.
float
f
= 12.3;
02.
03.
float
*
pf = &f;
04.
//
static cast<>
05.
06.
//
成功編譯, n = 12
07.
08.
int
n
=
static_cast
(f);
09.
10.
//
錯誤,指向的類型是無關的(譯註:即指針變量pf是float類型,現在要被轉換爲int類型)
11.
//int*
pn = static_cast(pf);
12.
13.
//成功編譯
14.
15.
void
*
pv =
static_cast
(pf);
16.
17.
//成功編譯,
但是 *pn2是無意義的內存(rubbish)
18.
19.
int
*
pn2 =
static_cast
(pv);
20.
//
reinterpret_cast<>
21.
22.
//錯誤,編譯器知道你應該調用static_cast<>
23.
24.
//int
i = reinterpret_cast(f);
25.
26.
//成功編譯,
但是 *pn 實際上是無意義的內存,和 *pn2一樣
27.
28.
int
*
pi =
reinterpret_cast
(pf);
簡而言之,static_cast<> 將嘗試轉換,舉例來說,如float-到-integer,而reinterpret_cast<>簡單改變編譯器的意圖重新考慮那個對象作爲另一類型。
指針類型(Pointer Types)
指針轉換有點複雜,我們將在本文的剩餘部分使用下面的類:
01.
class
CBaseX
02.
03.
{
04.
05.
public
:
06.
07.
int
x;
08.
09.
CBaseX()
{ x = 10; }
10.
11.
void
foo()
{
printf
(
"CBaseX::foo()
x=%d\n"
,
x); }
12.
13.
};
14.
class
CBaseY
15.
16.
{
17.
18.
public
:
19.
20.
int
y;
21.
22.
int
*
py;
23.
24.
CBaseY()
{ y = 20; py = &y; }
25.
26.
void
bar()
{
printf
(
"CBaseY::bar()
y=%d, *py=%d\n"
,
y, *py);
27.
}
28.
29.
};
30.
class
CDerived
:
public
CBaseX,
public
CBaseY
31.
32.
{
33.
34.
public
:
35.
36.
int
z;
37.
38.
};
情況1:兩個無關的類之間的轉換
01.
//
Convert between CBaseX* and CBaseY*
02.
03.
//
CBaseX* 和 CBaseY*之間的轉換
04.
05.
CBaseX*
pX =
new
CBaseX();
06.
07.
//
Error, types pointed to are unrelated
08.
09.
//
錯誤, 類型指向是無關的
10.
11.
//
CBaseY* pY1 = static_cast(pX);
12.
13.
//
Compile OK, but pY2 is not CBaseX
14.
15.
//
成功編譯, 但是 pY2 不是CBaseX
16.
17.
CBaseY*
pY2 =
reinterpret_cast
(pX);
18.
19.
//
System crash!!
20.
21.
//
系統崩潰!!
22.
23.
//
pY2->bar();
正如我們在泛型例子中所認識到的,如果你嘗試轉換一個對象到另一個無關的類static_cast<>將失敗,而reinterpret_cast<>就總是成功“欺騙”編譯器:那個對象就是那個無關類。
情況2:轉換到相關的類
原文章:後面看改後
01.
1.
CDerived* pD =
new
CDerived();
02.
03.
2.
printf
(
"CDerived*
pD = %x\n"
,
(
int
)pD);
04.
05.
3.
06.
07.
4.
//
static_cast<> CDerived* -> CBaseY* -> CDerived*
08.
09.
//成功編譯,隱式static_cast<>轉換
10.
11.
5.
CBaseY* pY1 = pD;
12.
13.
6.
printf
(
"CBaseY*
pY1 = %x\n"
,
(
int
)pY1);
14.
15.
//
成功編譯, 現在 pD1 = pD
16.
17.
7.
CDerived* pD1 =
static_cast
(pY1);
18.
19.
8.
printf
(
"CDerived*
pD1 = %x\n"
,
(
int
)pD1);
20.
21.
9.
22.
23.
10.
//
reinterpret_cast
24.
25.
//
成功編譯, 但是 pY2 不是 CBaseY*
26.
27.
11.
CBaseY* pY2 =
reinterpret_cast
(pD);
28.
29.
12.
printf
(
"CBaseY*
pY2 = %x\n"
,
(
int
)pY2);
30.
31.
13.
32.
33.
14.
//
無關的 static_cast<>
34.
35.
15.
CBaseY* pY3 =
new
CBaseY();
36.
37.
16.
printf
(
"CBaseY*
pY3 = %x\n"
,
(
int
)pY3);
38.
39.
//
成功編譯,儘管 pY3 只是一個 "新 CBaseY()"
40.
41.
17.
CDerived* pD3 =
static_cast
(pY3);
42.
43.
18.
printf
(
"CDerived*
pD3 = %x\n"
,
(
int
)pD3);
01.
----------------------
輸出 ---------------------------
02.
03.
CDerived*
pD = 392fb8
04.
05.
CBaseY*
pY1 = 392fbc
06.
07.
CDerived*
pD1 = 392fb8
08.
09.
CBaseY*
pY2 = 392fb8
10.
11.
CBaseY*
pY3 = 390ff0
12.
13.
CDerived*
pD3 = 390fec
//CBaseX* pX = new CBaseX();
//CBaseY* pY2 = reinterpret_cast<CBaseY*>(pX);
//////純繼承沒虛函數
CDerived* pD = new CDerived();
printf("&pD=%x\n",&pD);//證明建立任何類型指針是無關的,都是會在某一地方創建這個指針,不相關聯地址.指向的值很近+4-4
printf("*pD=%x\n",(int *)*(&pD));//這纔是實質,簡成pD
printf("CDerived* pD = %x\n", (int)pD);
// static_cast<> CDerived* -> CBaseY* -> CDerived*
//成功編譯,隱式static_cast<>轉換
CBaseY* pY1 = pD;//一樣是*(&pD) //隱藏轉換+4
printf("CBaseY* pY1 = %x\n", (int)pY1);
// 成功編譯, 現在 pD1 = pD
CDerived* pD1 = static_cast<CDerived*>(pY1); //-4
printf("&pD1=%x\n",&pD1);
printf("*pD1=%x\n",*pD1);
printf("CDerived* pD1 = %x\n", (int)pD1);
// reinterpret_cast
// 成功編譯, 但是 pY2 不是 CBaseY*
CBaseY* pY2 = reinterpret_cast<CBaseY*>(pD); //因爲是重樣解釋,所以這裏指針沒有像static_cast那樣+4,感覺沒意義
printf("CBaseY* pY2 = %x\n", (int)pY2);
//pY2->bar();//這裏是調用不了的,因爲實際地址不對!!!明白了!所以用重新解釋的時候心裏要有數,明白真正的地址
// 無關的 static_cast<>
CBaseY* pY3 = new CBaseY();
printf("CBaseY* pY3 = %x\n", (int)pY3);
// 成功編譯,儘管 pY3 只是一個 "新 CBaseY()"
CDerived* pD3 = static_cast<CDerived*>(pY3);
printf("CDerived* pD3 = %x\n", (int)pD3);
注意:在將CDerived*用隱式 static_cast<>轉換到CBaseY*(第5行)時,結果是(指向)CDerived*(的指針向後) 偏移了4(個字節)(譯註:4爲int類型在內存中所佔字節數)。爲了知道static_cast<> 實際如何,我們不得不要來看一下CDerived的內存佈局。
CDerived的內存佈局(Memory Layout)
如圖所示,CDerived的內存佈局包括兩個對象,CBaseX 和 CBaseY,編譯器也知道這一點。因此,當你將CDerived* 轉換到 CBaseY*時,它給指針添加4個字節,同時當你將CBaseY*轉換到CDerived*時,它給指針減去4。然而,甚至它即便不是一個CDerived你也可以這樣做。
當然,這個問題只在如果你做了多繼承時發生。在你將CDerived轉換 到 CBaseX時static_cast<> 和 reinterpret_cast<>是沒有區別的。
情況3:void*之間的向前和向後轉換
因爲任何指針可以被轉換到void*,而void*可以被向後轉換到任何指針(對於static_cast<> 和 reinterpret_cast<>轉換都可以這樣做),如果沒有小心處理的話錯誤可能發生。
01.
CDerived*
pD =
new
CDerived();
02.
03.
printf
(
"CDerived*
pD = %x\n"
,
(
int
)pD);
CBaseY*
pY = pD;
//
成功編譯, pY = pD + 4
05.
printf
(
"CBaseY*
pY = %x\n"
,
(
int
)pY);
void
*
pV1 = pY;
//成功編譯,
pV1 = pY
printf
(
"void*
pV1 = %x\n"
,
(
int
)pV1);
//
pD2 = pY, 但是我們預期 pD2 = pY - 4
CDerived* pD2 =
static_cast
(pV1);
13.
14.
printf
(
"CDerived*
pD2 = %x\n"
,
(
int
)pD2);
15.
16.
//
系統崩潰
17.
18.
//
pD2->bar();
01.
----------------------
輸出 ---------------------------
02.
03.
CDerived*
pD = 392fb8
04.
05.
CBaseY*
pY = 392fbc
06.
07.
void
*
pV1 = 392fbc
08.
09.
CDerived*
pD2 = 392fbc
一旦我們已經轉換指針爲void*,我們就不能輕易將其轉換回原類。在上面的例子中,從一個void* 返回CDerived*的唯一方法是將其轉換爲CBaseY*然後再轉換爲CDerived*。
但是如果我們不能確定它是CBaseY* 還是 CDerived*,這時我們不得不用dynamic_cast<> 或typeid[2]。
註釋:
1. dynamic_cast<>,從另一方面來說,可以防止一個泛型CBaseY* 被轉換到CDerived*。
2. dynamic_cast<>需要類成爲多態,即包括“虛”函數,並因此而不能成爲void*。
參考:
1. [MSDN] C++ Language Reference -- Casting
2. Nishant Sivakumar, Casting Basics - Use C++ casts in your VC++.NET programs
3. Juan Soulie, C++ Language Tutorial: Type Casting