型別轉換
將給定型別的一個表示式轉換為另一種型別稱為
型別轉換。我們已經見過一些型別轉換的方法
隱式轉換
隱式轉換不需要任何運算子。當一個值被複制到相容型別時,它們會自動執行。例如
1 2 3
|
short a=2000;
int b;
b=a;
|
這裡,
a的值從
short為
升級為int
short為
升級為,
升級為為
float,
double為
升級為...)以及與
bool之間的轉換,還有一些指標轉換。其中一些轉換可能意味著精度的損失,編譯器會發出警告。可以使用顯式轉換來避免此警告。
隱式轉換還包括建構函式或運算子轉換,它們影響包含特定建構函式或運算子函式以執行轉換的類。例如
1 2 3 4 5
|
class A {};
class B { public: B (A a) {} };
A a;
B b=a;
|
這裡,在
class A和
和class B
B有一個建構函式接受
A類的物件作為引數。因此,允許從
A為
B進行隱式轉換。
顯式轉換
C++ 是一種強型別語言。許多轉換,特別是那些意味著對值進行不同解釋的轉換,都需要顯式轉換。我們已經見過兩種顯式型別轉換的表示法:函式式轉換和 C 風格轉換。
1 2 3 4
|
short a=2000;
int b;
b = (int) a; // c-like cast notation
b = int (a); // functional notation
|
這些顯式轉換運算子的功能足以滿足基本資料型別的大多數需求。然而,這些運算子可以不加區分地應用於類和類指標,這可能導致程式碼雖然在語法上正確,但會導致執行時錯誤。例如,以下程式碼在語法上是正確的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
// class type-casting
#include <iostream>
using namespace std;
class CDummy {
float i,j;
};
class CAddition {
int x,y;
public:
CAddition (int a, int b) { x=a; y=b; }
int result() { return x+y;}
};
int main () {
CDummy d;
CAddition * padd;
padd = (CAddition*) &d;
cout << padd->result();
return 0;
}
|
|
該程式聲明瞭一個指向
CAddition的指標,但隨後使用顯式型別轉換將其賦值給了一個指向另一個不相容型別的物件的引用。
1
|
padd = (CAddition*) &d;
|
傳統的顯式型別轉換允許將任何指標轉換為任何其他指標型別,而不管它們指向的型別。隨後對成員
result的呼叫將導致執行時錯誤或意外的結果。
為了控制這類類之間的轉換,我們有四種特定的轉換運算子:
dynamic_cast,
reinterpret_cast,
static_cast和
const_cast它們的格式是跟隨新型別,用尖括號(
<>)括起來,緊接著是括號內的要轉換的表示式。
dynamic_cast <新型別> (表示式)
reinterpret_cast <新型別> (表示式)
static_cast <新型別> (表示式)
const_cast <新型別> (表示式)
這些表示式的傳統型別轉換等價物是
(新型別) 表示式
新型別 (表示式)
但它們各自具有特殊的特性。
dynamic_cast
dynamic_cast只能用於指向物件的指標和引用。其目的是確保型別轉換的結果是所請求類的有效完整物件。
因此,
dynamic_cast在將類轉換為其基類之一時,總是成功的。
1 2 3 4 5 6 7 8
|
class CBase { };
class CDerived: public CBase { };
CBase b; CBase* pb;
CDerived d; CDerived* pd;
pb = dynamic_cast<CBase*>(&d); // ok: derived-to-base
pd = dynamic_cast<CDerived*>(&b); // wrong: base-to-derived
|
此程式碼段中的第二個轉換將產生編譯錯誤,因為基類到派生類的轉換不允許使用
dynamic_cast,除非基類是多型的。
當一個類是多型的時,
dynamic_cast在執行時執行特殊檢查,以確保表示式產生所請求類的有效完整物件。
|
// dynamic_cast
#include <iostream>
#include <exception>
using namespace std;
class CBase { virtual void dummy() {} };
class CDerived: public CBase { int a; };
int main () {
try {
CBase * pba = new CDerived;
CBase * pbb = new CBase;
CDerived * pd;
pd = dynamic_cast<CDerived*>(pba);
if (pd==0) cout << "Null pointer on first type-cast" << endl;
pd = dynamic_cast<CDerived*>(pbb);
if (pd==0) cout << "Null pointer on second type-cast" << endl;
} catch (exception& e) {cout << "Exception: " << e.what();}
return 0;
}
|
Null pointer on second type-cast |
相容性注意事項 dynamic_cast需要執行時型別資訊(RTTI)來跟蹤動態型別。一些編譯器支援此功能作為選項,該選項預設停用。必須啟用此功能才能使dynamic_cast正常工作以進行執行時型別檢查。
|
程式碼嘗試執行兩個動態轉換,從型別為
CBase* (
pba和
pbb的物件指標,轉換為型別為
CDerived*的物件指標,但只有第一個成功。注意它們各自的初始化。
1 2
|
CBase * pba = new CDerived;
CBase * pbb = new CBase;
|
儘管兩者都是型別為
CBase*,
pba的指標,但
CDerived指向的是一個型別為
pbb的指標,但
CBase指向的是一個型別為
dynamic_cast,
pba指向的是一個完整的類物件
CDerived的迭代器,而
pbb指向的是一個類物件
CBase,而
CDerived.
當
dynamic_cast無法轉換指標,因為它不是所需類的完整物件(如上例中的第二次轉換),它會返回一個空指標來表示失敗。如果
dynamic_cast用於轉換為引用型別但轉換不可行,則會丟擲型別為
bad_cast的異常。
dynamic_cast還可以轉換空指標,甚至可以轉換不相關類指標,還可以將任何型別的指標轉換為 void 指標(
void*).
static_cast
static_cast可以執行相關類指標之間的轉換,不僅可以從派生類到其基類,還可以從基類到其派生類。這確保了至少類是相容的,如果轉換了正確的物件,但執行時沒有執行任何安全檢查來驗證被轉換的物件實際上是目標型別的完整物件。因此,程式設計師有責任確保轉換是安全的。另一方面,避免了
dynamic_cast的型別安全檢查的開銷。
1 2 3 4
|
class CBase {};
class CDerived: public CBase {};
CBase * a = new CBase;
CDerived * b = static_cast<CDerived*>(a);
|
這將是有效的,儘管
和 b將指向一個不完整的類物件,並且如果解引用它可能會導致執行時錯誤。
static_cast還可以用於執行任何其他可以隱式執行的非指標轉換,例如基本型別之間的標準轉換。
1 2
|
double d=3.14159265;
int i = static_cast<int>(d);
|
或具有顯式建構函式或運算子函式的類之間的任何轉換,如上面“隱式轉換”中所述。
reinterpret_cast
reinterpret_cast將任何指標型別轉換為任何其他指標型別,即使是不相關類的指標。操作結果是將值從一個指標到另一個指標的簡單二進位制複製。允許所有指標轉換:被指向的內容或指標型別本身都不會被檢查。
它還可以將指標轉換為整數型別或從整數型別轉換。此整數值表示指標的格式是平臺特定的。唯一保證的是,一個足夠大的指標轉換為整數型別,可以完全包含它,就能夠被轉換回一個有效的指標。
可以由
reinterpret_cast執行但不能由
static_cast執行的轉換是低階操作,其解釋會產生系統特定的程式碼,因此不具有可移植性。例如。
1 2 3 4
|
class A {};
class B {};
A * a = new A;
B * b = reinterpret_cast<B*>(a);
|
這是有效的 C++ 程式碼,儘管它意義不大,因為現在我們有一個指向不相容類物件的指標,因此解引用它是不安全的。
const_cast
這種型別的轉換會操縱物件的 constness,無論是設定還是移除。例如,為了將 const 引數傳遞給需要非 const 引數的函式。
|
// const_cast
#include <iostream>
using namespace std;
void print (char * str)
{
cout << str << endl;
}
int main () {
const char * c = "sample text";
print ( const_cast<char *> (c) );
return 0;
}
|
sample text |
typeid
typeid允許檢查表示式的型別。
typeid (表示式)
此運算子返回對型別物件的常量引用
type_info定義在標準標頭檔案
<typeinfo>中。返回的值可以使用運算子進行比較,
==和
!=或透過使用其
name()成員來獲取表示資料型別或類名稱的空終止字元序列。
|
// typeid
#include <iostream>
#include <typeinfo>
using namespace std;
int main () {
int * a,b;
a=0; b=0;
if (typeid(a) != typeid(b))
{
cout << "a and b are of different types:\n";
cout << "a is: " << typeid(a).name() << '\n';
cout << "b is: " << typeid(b).name() << '\n';
}
return 0;
}
|
a and b are of different types:
a is: int *
b is: int |
當
typeid當應用於類時,
typeid使用 RTTI 來跟蹤動態型別。當 typeid 應用於其型別為多型類的表示式時,結果是最派生完整物件的型別。
|
// typeid, polymorphic class
#include <iostream>
#include <typeinfo>
#include <exception>
using namespace std;
class CBase { virtual void f(){} };
class CDerived : public CBase {};
int main () {
try {
CBase* a = new CBase;
CBase* b = new CDerived;
cout << "a is: " << typeid(a).name() << '\n';
cout << "b is: " << typeid(b).name() << '\n';
cout << "*a is: " << typeid(*a).name() << '\n';
cout << "*b is: " << typeid(*b).name() << '\n';
} catch (exception& e) { cout << "Exception: " << e.what() << endl; }
return 0;
}
|
a is: class CBase *
b is: class CBase *
*a is: class CBase
*b is: class CDerived |
注意:來自成員名稱的字串 type_info 取決於你編譯器和庫的特定實現。它不一定是具有典型型別名稱的簡單字串,就像用於生成此輸出的編譯器一樣。
注意,
typeid考慮指標的型別是其指標型別本身(兩者都是
a和
和 bj
class CBase *)。然而,當
typeid應用於物件(如
*a和
*b)
typeid)時,它會產生它們的動態型別(即其最派生完整物件的型別)。
如果型別
typeid求值的是一個後跟解引用運算子(
*)的指標,並且該指標的值為空,
typeid會丟擲一個
bad_typeid異常。
上面示例中的編譯器生成的名稱
type_info::name 是使用者可讀的,但這並非必需:編譯器可以只返回任何字串。