沒有學不會的C++:顯示類型轉換(Casting)

C++ 中定義了 4 種顯示類型轉換,初學 C++,不免以爲這一部份內容複雜、難以理解(固然我也不例外),但掌握它又是頗有必要的,畢竟事物的存在,必有它存在的道理,而這個道理,就是相比其餘設計而言(例如傳統的 C 風格的類型轉換),C++ 的類型轉換可以減小出錯的機率,咱們在前面的文章中也提到過這個設計原則,想成爲一名優秀的程序員,該原則值得你謹記在心。java

這 4 種類型轉換分別是:ios

  1. static_cast
  2. dynamic_cast
  3. const_cast
  4. reinterpret_cast

static_cast

static_cast 既能夠在對象間進行類型轉換(前提是有相應的轉換構造函數或轉換操做符支持),也能夠在指針或引用間作類型轉換(通常是父類和子類之間的向上或向下轉換),表達式以下程序員

static_cast<new_type>(expression);
複製代碼

下面是一個簡單的例子,將 float 類型轉換爲 int 類型express

#include <iostream>
using namespace std;

int main() {
  float f = 3.5;
  int i1 = f;   // C 語言的用法
  int i2 = static_cast<int>(f);
  cout << i1 << endl;
  cout << i2 << endl;
  return 0;
}
複製代碼

稍微修改上面的代碼,看看若是類型轉換針對的是指針類型,會發生什麼編程

#include <iostream>
using namespace std;

int main() {
  char c = 'a';
  int* i1 = (int*)&c;		// C 風格,compile ok
  int* i2 = static_cast<int*>(&c); // compile error
  cout << *i1 << endl;
  return 0;
}
複製代碼

能夠看到,在 C++ 中,編譯器不容許你將一個 char* 類型的指針轉換爲 int* 類型的,由於這樣作可能會產生整型溢出的錯誤,雖然你依然能夠採用 C 風格的強制類型轉換達到這一目的,但不建議你這麼作,這也是在 C++ 中,爲何要用 _cast 來取代 C 類型轉換的緣由之一安全

void* 類型的指針是能夠轉換爲任何其餘類型的指針的,這一點也是 C 中經常使用的技巧:bash

int score = 90;
void* p = &score;
int* pscore = static_cast<int*>(p);
複製代碼

此外,static_cast 很適合將隱式類型轉換顯示化,一般,使用顯示類型轉換是較好的編程習慣。依然以上一篇文章的例子爲例:函數

class dog {
public:
  dog(string name) {m_name = name;}           // 轉換構造函數
  operator string () const { return m_name; } // 轉換操做符
private:
  string m_name;
};

int main() {
  string dogname = "dog";
  dog d1 = dogname;                             // a1. 隱式 string->dog
  dog d2 = static_cast<dog>(dogname);           // a2. 顯示 string->dog
  string other_dog_name = d2;                   // b1. 隱式 dog->string
  string my_dog_name = static_cast<string>(d2); // b2. 顯示 dog->string
  cout << my_dog_name << endl;
  cout << other_dog_name << endl;
  return 0;
};
複製代碼

上面的代碼中,註釋 a1b1 分別是利用隱式構造函數和隱式操做符實現的隱式類型轉換,而註釋 a2b2 將它們顯示化了 。性能

前文提到了 static_cast 還能夠在父子類之間進行向上或向下的類型轉換,要注意的是,子類的繼承做用域須要是 public,且轉換的對象須要是指針或引用類型this

#include <vector>
#include <iostream>
 
class B {
  public:
    void hello() const {
        std::cout << "Hello world, this is B!\n";
    }
};
class D : public B {
  public:
    void hello() const {
        std::cout << "Hello world, this is D!\n";
    }
};
int main()
{
  D d;
  B& br = d; // 經過隱式轉換向上轉型
  br.hello();
  D& another_d = static_cast<D&>(br); // 向下轉型
  another_d.hello();
}
複製代碼

以上代碼會輸出

Hello world, this is B!
Hello world, this is D!
複製代碼

dynamic_cast

dynamic_cast 主要針對的是有繼承關係的類的指針和引用,且要求類中必需要有 virtual 關鍵字,這樣才能提供運行時類型檢查,當類型檢查發現不匹配時,dynamic_cast 返回 0;實際運用中,dynamic_cast 主要用於向下轉型。在 cppreference.com 頁面,提供了很好的例子

#include <iostream>
struct Base {
    virtual ~Base() {}
}; 
struct Derived: Base {
    virtual void name() {}
};
int main() {
    Base* b1 = new Base;
    if(Derived* d = dynamic_cast<Derived*>(b1)) {
        std::cout << "downcast from b1 to d successful\n";
        d->name(); // 調用安全
    } 
    Base* b2 = new Derived; // 向上轉型,能夠用 dynamic_cast ,但沒必要須
    if(Derived* d = dynamic_cast<Derived*>(b2)) {
        std::cout << "downcast from b2 to d successful\n";
        d->name(); // 調用安全
    }
    delete b1;
    delete b2;
}
複製代碼

上面代碼僅輸出

downcast from b2 to d successful
複製代碼

另外一個 cout 沒有輸出的緣由是 Base* b1Derived* d 類型不一致,轉型失敗,dynamic_cast 返回爲 0。

一樣是運行時多態,使用 C++ 的虛函數列表要比 dynamic_cast 在性能上有很大的優點,並且前者可以寫出更通用的代碼(更少的類型相關的硬編碼)。

const_cast

const_cast 在用法上要簡單不少,它可以移除 const 引用const 指針 上的 const 屬性,須要注意的是

  1. const引用const指針 指向的變量不能也是 const 類型
  2. const_cast 不能做用於函數指針或成員函數指針上

典型的使用方法以下:

int i = 3;
const int& cri = i;
const int* cpi = &i;
const_cast<int&>(cri) = 4;   // i = 4
*const_cast<int*>(cpi) = 5; // i = 5
複製代碼

上面的 i 不能夠是 const 類型,不然程序行爲是未定義的,如

const i = 3;
const int& cri = i;
const_cast<int&>(cri) = 4;   // 可編譯,但行爲未定義
複製代碼

reinterpret_cast

reinterpret_cast 是 C++ 中最危險的轉型操做,它能夠將指針所指的類型從新解釋爲其餘任意類型,且沒有任何檢驗機制。reinterpret_cast 主要用於這樣的場景:先將一個指針轉型爲另外一種類型(一般爲 void*),在使用前,再把它轉型爲原始類型,以下:

int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);
複製代碼

但實際上,這樣的使用方法徹底能夠用 static_cast 代替,即

int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);
複製代碼

因此,若是不能保證必定安全,儘可能避免使用這種轉型。

總結

C++ 除了支持以上 4 種轉型方式,還兼容 C 風格的轉型,但讀了本文後,你能夠把 C 風格的轉型當作是 static_castconst_castreinterpret_cast 這 3 種轉型的並集。因此,咱們更應該使用 C++

風格的轉型,而不是 C 風格的,緣由是:

  1. 在作 coding review 時,你能夠經過搜索 _cast 關鍵字,來查看代碼中哪些地方用到了轉型操做
  2. 每種 C++ 轉型都有其使用限制,而 C 轉型卻沒有
  3. C++ 的轉型具有運行時識別能力

參考:

相關文章
相關標籤/搜索