C++ 中定義了 4 種顯示型別轉換,初學 C++,難免覺得這一部分內容複雜、難以理解(當然我也不例外),但掌握它又是很有必要的,畢竟事物的存在,必有它存在的道理,而這個道理,就是相比其他設計而言(例如傳統的 C 風格的型別轉換),C++ 的型別轉換能夠減少出錯的概率,我們在前面的文章中也提到過這個設計原則,想成為一名優秀的程式設計師,該原則值得你謹記在心。
這 4 種型別轉換分別是:
- static_cast
- dynamic_cast
- const_cast
- reinterpret_cast
static_cast
static_cast
既可以在物件間進行型別轉換(前提是有相應的轉換建構函式或轉換操作符支援),也可以在指標或引用間做型別轉換(一般是父類和子類之間的向上或向下轉換),表示式如下
static_cast<new_type>(expression);
複製程式碼
下面是一個簡單的例子,將 float
型別轉換為 int
型別
#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 中常用的技巧:
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;
};
複製程式碼
上面的程式碼中,註釋 a1
和 b1
分別是利用隱式建構函式和隱式操作符實現的隱式型別轉換,而註釋 a2
和 b2
將它們顯示化了 。
前文提到了 static_cast
還可以在父子類之間進行向上或向下的型別轉換,要注意的是,子類的繼承作用域需要是 public
,且轉換的物件需要是指標或引用型別
#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* b1
和 Derived* d
型別不一致,轉型失敗,dynamic_cast
返回為 0。
同樣是執行時多型,使用 C++ 的虛擬函式列表要比 dynamic_cast
在效能上有很大的優勢,而且前者能夠寫出更通用的程式碼(更少的型別相關的硬編碼)。
const_cast
const_cast
在用法上要簡單很多,它能夠移除 const 引用
或 const 指標
上的 const
屬性,需要注意的是
- 被
const引用
或const指標
指向的變數不能也是const
型別 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_cast
、const_cast
和 reinterpret_cast
這 3 種轉型的並集。所以,我們更應該使用 C++
風格的轉型,而不是 C 風格的,原因是:
- 在做 coding review 時,你可以通過搜尋
_cast
關鍵字,來檢視程式碼中哪些地方用到了轉型操作 - 每種 C++ 轉型都有其使用限制,而 C 轉型卻沒有
- C++ 的轉型具備執行時識別能力
參考: