C++類型轉換
本節解決什麼問題
C++ 中經常會遇到“把一種類型當成另一種類型使用”的場景,例如:
- 把
double轉成int。 - 把
enum class轉成整數。 - 把基類指針安全地轉成派生類指針。
- 臨時去掉
const限定,適配舊接口。 - 在底層代碼裡把指針、整數、字節視圖互相轉換。
C 風格強轉寫法 (目标类型)表达式 能做很多事,但問題也在這裡:它太“萬能”,讀代碼的人很難一眼看出這次轉換到底是普通數值轉換、繼承層級轉換、去掉 const,還是危險的底層重解釋。
C++ 提供了四個更明確的類型轉換運算符,讓轉換意圖寫在代碼裡。
這個特性是什麼
C++ 的四種顯式類型轉換是:
| 轉換方式 | 主要用途 | 安全程度 |
|---|---|---|
static_cast<T>(expr) | 常規類型轉換,如數值、枚舉、父子類上行轉換 | 常用,編譯期檢查 |
dynamic_cast<T>(expr) | 多態類型的安全向下轉換 | 運行期檢查,失敗返回 nullptr 或拋異常 |
const_cast<T>(expr) | 添加或移除 const / volatile 限定 | 要非常剋制 |
reinterpret_cast<T>(expr) | 底層二進制重解釋,如指針和整數互轉 | 最危險,少用 |
原則:能不用強轉就不用;必須強轉時,優先選擇語義最窄、最能說明意圖的轉換。
C++ 標準版本
C++98 就已經提供這四種轉換寫法,實際工程中依然推薦使用它們替代 C 風格強轉。
需要的頭文件
類型轉換本身不需要額外頭文件。示例代碼使用到的庫需要對應頭文件,例如:
#include <cstdint>
#include <iostream>
運行/觀察結果: 這段是頭文件示例,放到完整程序頂部即可。
基本語法
目标类型 value = static_cast<目标类型>(表达式);
目标类型 value = dynamic_cast<目标类型>(表达式);
目标类型 value = const_cast<目标类型>(表达式);
目标类型 value = reinterpret_cast<目标类型>(表达式);
運行/觀察結果: 這段是語法格式說明,重點看四種轉換的寫法差異。
示例代碼
示例 1:static_cast 處理常規轉換
static_cast 適合明確、常規、編譯期能檢查的轉換。比如數值轉換、enum class 轉整數、父類指針指向子類對象等。
#include <iostream>
enum class Status
{
ok = 200,
not_found = 404
};
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
double score = 89.7;
int integer_score = static_cast<int>(score);
Status status = Status::not_found;
int status_code = static_cast<int>(status);
std::cout << "integer_score = " << integer_score << "\n";
std::cout << "status_code = " << status_code << "\n";
// 返回 0 表示程序正常结束。
return 0;
}
運行結果:
integer_score = 89
status_code = 404
示例 2:dynamic_cast 安全判斷真實對象類型
dynamic_cast 常用於多態基類。向下轉換時,它會在運行期檢查對象真實類型。
#include <iostream>
struct Animal
{
virtual ~Animal() = default;
};
struct Cat : Animal
{
void meow() const
{
std::cout << "cat: meow\n";
}
};
struct Dog : Animal
{
};
void try_meow(Animal* animal)
{
Cat* cat = dynamic_cast<Cat*>(animal);
if (cat != nullptr)
{
cat->meow();
}
else
{
std::cout << "not a cat\n";
}
}
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
Cat cat;
Dog dog;
try_meow(&cat);
try_meow(&dog);
// 返回 0 表示程序正常结束。
return 0;
}
運行結果:
cat: meow
not a cat
注意:
dynamic_cast用在指針上,轉換失敗會得到nullptr;用在引用上,轉換失敗會拋出std::bad_cast。它要求基類至少有一個虛函數,通常是虛析構函數。
示例 3:const_cast 只改變 const 限定
const_cast 只能添加或移除 const / volatile,不能把 int 轉成 double,也不能把不相關類型互轉。
#include <iostream>
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
int value = 10;
const int& readonly_ref = value;
int& writable_ref = const_cast<int&>(readonly_ref);
writable_ref = 20;
std::cout << "value = " << value << "\n";
// 返回 0 表示程序正常结束。
return 0;
}
運行結果:
value = 20
這個例子能修改成功,是因為真正的原始對象 value 不是 const,只是通過 const int& 觀察它。如果原始對象本身就是 const,再用 const_cast 強行修改就是未定義行為。
示例 4:reinterpret_cast 做底層重解釋
reinterpret_cast 表示“把這段二進制數據按另一種類型解釋”。它繞過了很多類型系統保護,通常只應該出現在非常底層的代碼裡。
#include <cstdint>
#include <iostream>
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
int value = 42;
int* p = &value;
std::uintptr_t raw = reinterpret_cast<std::uintptr_t>(p);
int* again = reinterpret_cast<int*>(raw);
std::cout << "*again = " << *again << "\n";
std::cout << std::boolalpha;
std::cout << "same pointer = " << (p == again) << "\n";
// 返回 0 表示程序正常结束。
return 0;
}
運行結果:
*again = 42
same pointer = true
這個例子只用於說明“指針可以轉成能容納指針的整數,再轉回來”。不要隨便拿轉換後的整數做地址計算,也不要把不相關對象強行解釋成另一個對象類型。
四種轉換怎麼選
| 需求 | 推薦寫法 | 說明 |
|---|---|---|
double 轉 int | static_cast<int>(x) | 明確可能丟失小數 |
enum class 轉整數 | static_cast<int>(e) | 枚舉類不會隱式轉整數 |
| 基類指針轉派生類指針,並且不確定真實類型 | dynamic_cast<Derived*>(p) | 轉換失敗可判斷 nullptr |
去掉 const 適配舊接口 | const_cast<T*>(p) | 前提是舊接口不會修改真正的 const 對象 |
| 指針和整數互轉、底層字節解釋 | reinterpret_cast<T>(x) | 非常底層,優先避免 |
C風格強轉為什麼不推薦
C 風格強轉寫起來短:
int n = (int)3.14;
運行/觀察結果: 這段會把 3.14 轉成 3,但讀代碼時看不出它屬於哪類轉換。
更推薦寫成:
int n = static_cast<int>(3.14);
運行/觀察結果: 這段同樣會得到 3,但轉換意圖更明確:這是常規數值轉換。
C 風格強轉可能在背後組合多種轉換能力,既可能像 static_cast,也可能像 const_cast,甚至接近 reinterpret_cast。代碼越底層、類型越複雜,這種“不說明白”的風險就越大。
常見錯誤
用 static_cast 做不安全向下轉換
Animal* animal = new Dog;
Cat* cat = static_cast<Cat*>(animal); // 危险:编译可能通过,但真实对象不是 Cat
運行/觀察結果: 這段是錯誤示例,真實對象類型不匹配時繼續使用 cat 會產生未定義行為。
如果不確定真實類型,應該使用 dynamic_cast。
修改真正的 const 對象
const int value = 10;
int& ref = const_cast<int&>(value);
ref = 20; // 未定义行为
運行/觀察結果: 這段是錯誤示例,const_cast 可以騙過編譯器,但不能讓真正的常量安全地變成可修改對象。
濫用 reinterpret_cast
double d = 3.14;
int* p = reinterpret_cast<int*>(&d); // 危险:把 double 对象当 int 对象访问
運行/觀察結果: 這段是錯誤示例,違反類型別名規則或對象表示假設時,行為不可預測。
使用建議
- 普通轉換優先用
static_cast。 - 多態向下轉換優先用
dynamic_cast,並檢查失敗情況。 const_cast只用於適配舊接口,不要用它修改真正的const對象。reinterpret_cast只放在非常底層、邊界清晰的代碼裡,並集中封裝。- 不要為了少打幾個字使用 C 風格強轉。
小結
static_cast:常規轉換,最常用。dynamic_cast:多態類型安全向下轉換。const_cast:只改變const/volatile限定。reinterpret_cast:底層重解釋,風險最高。
一句話記憶:能不用就不用;必須用時,把轉換意圖寫清楚。