C++類型轉換


隱式轉換

在賦值給一個兼容類型會出現隱式類型轉換.比如下面這個例子.

short a=2000;
int b;
b=a;

在以上例子中.值從short自動提升到int,這是標准轉換。標准轉換影響基本數據類型,它在類型數字類型之間(short to intint to floatdouble 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異常.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM