隱式轉換
在賦值給一個兼容類型會出現隱式類型轉換.比如下面這個例子.
short a=2000; int b; b=a;
在以上例子中.值從short自動提升到int,這是標准轉換。標准轉換影響基本數據類型,它在類型數字類型之間(short
to int
, int
to float
, double
to int
...),
布爾類型和一些指針之間執行。
從小的數字類型轉換成int,或者float to double叫做類型提升。這樣的轉換保證生成相同的值。但是其他一些轉換不保證一定生成同樣的值。
1.如果負數轉換成unsigned 類型。-1轉換成最大無符號值。
2.其他類型轉bool或者由bool轉換成其他類型。false轉成0(數值類型)nullptr(指針類型)。true轉換成1。
3.由浮點數轉換成整數類型.值被截斷(小數部分直接被移除).如果剩余的部分超出整數能表示的范圍,結果未知。
4.如果轉換發生在相同的數值類型.整數-整數,浮點數-浮點數.轉換是合法的.但是具體的值是多少由實現着指定(可能不具有移植性)。
某些轉換可能會丟失精度,編譯器會通知出現精度丟失,但是顯式的轉換不會出現通知。
對於非基本類型,數組和函數隱式轉換成指針類型。指針之間轉換根據以下規則
1.null指針允許轉換成任意類型。
2.任何類型指針都能轉換成void類型指針。
3.指針向上轉換,派生類指針能夠轉換成任意基類指針,前提是沒有const,volatile修飾。
類的隱式轉換
在類的世界里,類的轉換由以下三個成員函數控制。
1.只有一個參數的構造函數:允許從一個特定類型隱式轉換來初始化對象。
2.賦值操作:允許在賦值的時候出現隱式轉換。
3.類型轉換操作:允許隱式轉換一個特定類型。
// implicit conversion of classes: #include <iostream> using namespace std; class A {}; class B { public: // conversion from A (constructor): B (const A& x) {} // conversion from A (assignment): B& operator= (const A& x) {return *this;} // conversion to A (type-cast operator) operator A() {return A();} }; int main () { A foo; B bar = foo; // calls constructor bar = foo; // calls assignment foo = bar; // calls type-cast operator return 0; }
類型轉換操作使用特殊的語法:它使用operator關鍵字后面跟上目標類型然后是一對圓括號。注意返回的是特定對象的類型,並且也沒有在operator關鍵字之前指定。
explicit關鍵字
在函數調用時,C++允許隱式轉換參數,這可能會引起錯誤。比如下面這個函數(來自上面的例子)
void fn (B arg) {}
這個函數的參數類型是B,但是它可以用A來調用。
fn(foo)
我們可以在構造函數上使用explicit關鍵字消除這個影響。
// explicit: #include <iostream> using namespace std; class A {}; class B { public: explicit B (const A& x) {} B& operator= (const A& x) {return *this;} operator A() {return A();} }; void fn (B x) {} int main () { A foo; B bar (foo); bar = foo; foo = bar; // fn (foo); // not allowed for explicit ctor. fn (bar); return 0; }
另外,在使用explicit標記構造函數后,不能使用類似賦值的方法隱式調用構造函數。比如下面這個不允許。
B bar = foo;
類型轉換函數也可以加上explicit關鍵字,效果和在構造函數上加一致。
foo = bar; //轉換函數加上explicit后,這個調用是錯誤的.
類型轉換
C++是一個強類型語言.有許多類型轉換不能隱式進行。特別是表示對值有不同解釋的轉換,這些類型轉換都需要顯示指定。
主要有倆種風格,函數型風格和C語言風格,如下。
double x = 10.3; int y; y = int (x); // functional notation y = (int) x; // c-like cast notation
函數風格轉換滿足大部分基本類型轉換。但在類和指針轉換到類的時候會混淆不清。這樣容易引起運行時錯誤,比如下面這個代碼,編譯時沒有任何錯誤。
// class type-casting #include <iostream> using namespace std; class Dummy { double i,j; }; class Addition { int x,y; public: Addition (int a, int b) { x=a; y=b; } int result() { return x+y;} }; int main () { Dummy d; Addition * padd; padd = (Addition*) &d; cout << padd->result(); return 0; }
程序聲明一個指針指向Addition,但是它被賦值了一個不相關的對象。
padd = (Addition*) &d;
自由的顯式類型轉換允許一個指針類型轉換成任何一個指針類型。上面的調用會導致一個運行時錯誤或者是一個不期望的結果。
為了控制這種不受控制的轉換,我們新增了四種特定的類型轉換。dynamic_cast,static_cast,reinterpret_cast,const_cast。
語法如下。
dynamic_cast <new_type> (expression) reinterpret_cast <new_type> (expression) static_cast <new_type> (expression) const_cast <new_type> (expression)
等價於傳統的類型轉換
(new_type) expression
new_type (expression)
但是以上的每一種類型轉換都有自己獨特的特點。
dynamic_cast
dynamic_cast僅能在指針或者引用上使用,當然也包含void*。它用來保證轉換的目標類型是一個完全合法的類型。這個轉換包括指針向上轉換(基類指針轉換成父類指針),有時候這個也叫做隱式轉換。同樣,dynamic_cast也被用來實現向下轉換,但也只是在多態類的時候。如下
// dynamic_cast #include <iostream> #include <exception> using namespace std; class Base { virtual void dummy() {} }; class Derived: public Base { int a; }; int main () { try { Base * pba = new Derived; Base * pbb = new Base; Derived * pd; pd = dynamic_cast<Derived*>(pba); if (pd==0) cout << "Null pointer on first type-cast.\n"; pd = dynamic_cast<Derived*>(pbb); if (pd==0) cout << "Null pointer on second type-cast.\n"; } catch (exception& e) {cout << "Exception: " << e.what();} return 0; }
Null pointer on second type-cast.
兼容提示: dynamic_cast需要運行時信息追蹤動態類型。某些編譯器這個功能默認是關閉的。為了保證dynamic_cast運行正確,請打開運行時類型檢查選項。
以上代碼嘗試執行倆次類型轉換,都是從基類轉換成子類,但是只有第一次是成功的。注意他們的初始化。
Base * pba = new Derived; Base * pbb = new Base;
雖然他們都是Base* 類型的指針,但是pba實際上指向的是Derived類型的指針,pbb的實際類型是Base*。因此當使用dynamic_cast轉換的時候,pba成功了,因為pbb指向的是Base,不是一個完整的Delived對象,所以轉換時失敗。
當轉換失敗的時候,dynamic_cast返回一個null指針表示轉換失敗。如果dynamic_cast轉換一個引用失敗,將會拋出bad_cast類型的異常。
dynamic_cast同樣也允許在指針上執行隱式轉換,null指針在倆個類型之間轉換(即使是沒有任何關聯的類型),轉換任何指針類型變成void* 類型指針。
static_cast
static_cast在倆個相關類型之間執行轉換,可以是往上轉換,也可以是往下轉換。轉換時不執行任何運行時檢查,因此也就不保證目標類型一定正確。所以,static_cast需要程序員保證轉換是安全的。與dynamic_cast相比,static_cast轉換更快。
class Base {}; class Derived: public Base {}; Base * a = new Base; Derived * b = static_cast<Derived*>(a);
這是一個合法的代碼,但是b指向的對象不是一個完整的對象,所以運行時解引用會拋出一個錯誤。
static_cast同樣也能用於隱式轉換(不僅僅是指向對象的指針)。
1.把void*轉換成任何類型的指針。這樣的轉換保證x->void*->x,即保證把void*轉換成以前的類型。
2.轉換數值,浮點數,枚舉類型到枚舉類型。
另外,static_cast也能在以下場景中使用。
1.顯式調用只有一個參數的構造函數或者賦值操作。
2.轉換成右值引用。
3.把enum轉換成int或者float。
4.把任何類型轉換成void,evaluating and discarding the value。
reinterpret_cast
reinterpret_cast轉換任意指針類型到其他指針類型,即使是不相關的類型也可以轉換。它轉換的結果僅僅只是復制二進制數據到目標類型,任何類型的轉換都可以使用reinterpret_cast。它既不檢查內容,也不檢查類型。它可以把指針轉換成一個數字或者把一個數字轉換成指針。當把一個數字轉換成指針時的結果由平台來決定。
它只保證把指針轉換成數值時,數值的寬度可以完全包含指針的內容。同樣,再次轉換回指針時也是完全合法的指針。
通過interpret_cast,而不是static_cast來轉換。這是一種根據類型來重新解釋二進制數據的低級別操作。大多數場景下結果根據平台而定,因此也就是失去了可移植性。
class A { /* ... */ }; class B { /* ... */ }; A * a = new A; B * b = reinterpret_cast<B*>(a);
代碼能編譯,但是沒有什么意義,因為a被轉換成了一個完全不相關類型,當b解引用的時候是不安全的。
const_cast
這個轉換類型操作指針的常量屬性,可能是添加常量屬性,也可能是移除常量屬性。比如下面這個為了傳遞一個常量指針到一個非常量指針。
// const_cast #include <iostream> using namespace std; void print (char * str) { cout << str << '\n'; } int main () { const char * c = "sample text"; print ( const_cast<char *> (c) ); return 0; }
sample text
以上樣例保證安全,因為函數沒有寫指針指向的內容。但是請注意。在移除指針的常量屬性后進行寫入操作,這個結果是未知的。
typeid
typeid (expression)
typeid檢查表達式的類型,返回<typeinfo>頭文件中定義的常量對象的引用。所有typeid的返回值可以通過==或者!=來比較,或者可以通過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在類上使用時,它使用運行時類型信息來跟蹤動態對象(RTTI)。如果在多態類上使用時,它返回子類的類型。
// typeid, polymorphic class #include <iostream> #include <typeinfo> #include <exception> using namespace std; class Base { virtual void f(){} }; class Derived : public Base {}; int main () { try { Base* a = new Base; Base* b = new Derived; 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() << '\n'; } return 0; }
a is: class Base * b is: class Base * *a is: class Base *b is: class Derived
注意,typeid返回類型的name()方法的結果,根據使用的編譯器和庫的不同而不同,它有可能不是一個簡單的字符串。
注意typeid是怎么考慮指針類型(a和b指針的是Base* 類型),當typeid在對象上使用時(*a和*b),typeid返回的是動態類型(*a是Base類型,*b是Derived類型)。
如果傳入的指針等於null,會拋出bad_typeid異常.