类型转换
需要预先了解:
1. 隐式类型转换
隐式类型转换(implicit conversion)由编译器自动完成,不需要程序员写出任何转换语法。它发生在赋值、传参、表达式求值等场景中,正确使用时十分自然,但某些情况下会悄悄引入错误。
1.1 数值转换与整型提升
小整型参与算术运算时,会先提升为 int,这叫整型提升(integral promotion):
char c = 'A';
int n = c; // char 提升为 int,n = 65
short s = 100;
int sum = s + 1; // s 先提升为 int 再参与加法
从窄到宽的数值转换是安全的:int 转 long、float 转 double 都不丢失信息。但从宽到窄的转换会截断或丢失精度,比如 double 转 int 直接丢掉小数部分:
double d = 3.99;
int x = d; // x = 3,小数部分被丢弃
浮点转整数的截断方向是向零取整(truncation),不是四舍五入,-3.99 转 int 得到 -3。
1.2 有符号与无符号的陷阱
有符号和无符号类型混合运算时,有符号值会被隐式转换为无符号类型,这是隐式转换最经典的坑:
#include <iostream>
int main()
{
int a = -1;
unsigned int b = 1;
std::cout << (a < b) << std::endl; // 输出 0(false)
}
a 被转换为 unsigned int 后变成 4294967295,自然不小于 1。类似地,-1 > 0U 在无符号视角下也是成立的。规则:转换时值按无符号类型的模(mod,即 2 的 N 次方,N 为位宽)取余,负数会"回绕"成很大的正数。
1.3 指针与类类型的隐式转换
指针相关的隐式转换:
- 数组名退化为指针:数组在表达式中自动退化为指向首元素的指针
- 向上转型(upcast):派生类指针/引用隐式转换为基类指针/引用,多态的基础
nullptr可以隐式转换为任意指针类型
int arr[3] = {1, 2, 3};
int* p = arr; // 数组名退化为指针
class Base {};
class Derived : public Base {};
Derived d;
Base* pb = &d; // 向上转型,隐式完成
类类型的隐式转换有两个来源:
- 单参数构造函数:定义了从别的类型"构造"本类型的路径
- 转换运算符:定义了本类型"变回"别的类型的路径
class Number
{
public:
Number(int v) : val(v) {}
operator int() const { return val; }
private:
int val;
};
Number num = 42; // int -> Number,调用构造函数
int back = num; // Number -> int,调用转换运算符
这种隐式转换在便利的同时也容易误触发,比如把 Number 意外传给只接受 int 的函数。如果不希望某个构造函数参与隐式转换,可以加 explicit 关键字禁止它。
2. C 风格类型转换
C 风格类型转换(C-style cast)写法是 (目标类型)表达式,语法最简单,但语义上它是一把"万能钥匙"——等价于依次尝试 const_cast、static_cast、static_cast + const_cast、reinterpret_cast、reinterpret_cast + const_cast,哪个能编译通过就用哪个:
double d = 3.14;
int x = (int)d; // 数值截断(static_cast 的活)
const char* str = "hello";
char* p = (char*)str; // 去掉 const(const_cast 的活)
int* ip = (int*)0x1234; // 位级重解释(reinterpret_cast 的活)
从三个角度分析它的问题:
- 可读性:
(int)看不出转换意图,是数值转换、去 const 还是位重解释?代码搜索时搜static_cast能精确定位,搜(int)会误伤一片 - 安全性:编译器完全不检查转换的合理性,允许任意组合,容易写出危险代码而不自知
- 性能:本身零运行时开销,但因为它混用了几种语义,一旦需要排查转换相关的 bug,定位成本很高
注:C++ 中应避免使用 C 风格转换,用四种具名 cast 明确表达转换意图。
3. static_cast
static_cast 用于"逻辑上相关"的类型之间,转换是否合法在编译期检查。典型用途:
- 数值类型互转、枚举与整数互转
void*与具体类型指针互转- 类层次中的向上转型,以及不带检查的向下转型
#include <iostream>
int main()
{
double d = 3.14;
int x = static_cast<int>(d); // 截断为 3
int n = 42;
void* vp = &n; // 任何指针都能隐式转 void*
int* ip = static_cast<int*>(vp); // void* 还原为具体指针
std::cout << x << ' ' << *ip << std::endl; // 输出 3 42
}
编译期检查意味着不相关的类型无法通过编译:
int n = 42;
double* dp = static_cast<double*>(&n); // 错误!int* 与 double* 不相关
编译时报错:
code.cpp:5:32: error: invalid static_cast from type 'int*' to type 'double*'
5 | double* dp = static_cast<double*>(&n);
安全性分析:编译期能拦截大部分错误组合;性能上零运行时开销,不生成任何检查代码;可读性上意图明确(“相关类型之间的转换”)。
注意:
static_cast的向下转型(基类指针转派生类指针)不做运行时检查,如果实际对象不是目标派生类型,解引用就是未定义行为——不确定时应该用dynamic_cast。
4. dynamic_cast
dynamic_cast 是运行时检查的向下转型,只能用于多态类型(含虚函数的类),依赖 RTTI(Run-Time Type Information,运行时类型信息):
#include <iostream>
class Base
{
public:
virtual ~Base() {} // 有虚函数才是多态类型
};
class Derived : public Base
{
public:
void hello() const { std::cout << "Derived" << std::endl; }
};
int main()
{
Base* b1 = new Derived();
Base* b2 = new Base();
Derived* d1 = dynamic_cast<Derived*>(b1); // 成功:实际类型就是 Derived
Derived* d2 = dynamic_cast<Derived*>(b2); // 失败:返回 nullptr
if (d1) d1->hello(); // 输出 Derived
std::cout << (d2 == nullptr) << std::endl; // 输出 1
}
行为规则:
- 指针转换失败返回
nullptr,因此总是需要判空 - 引用转换失败抛出
std::bad_cast异常(引用没有"空"的概念)
从三个角度分析:
- 安全性:最高——转换前在运行时核实对象的真实类型,失败有明确信号
- 性能:有运行时开销,需要查 RTTI 信息做类型比较,比
static_cast慢;虚函数表中的typeinfo指针参与判断 - 可读性:语义清晰,代码本身就表达了"我不确定实际类型,需要检查"
注:
dynamic_cast只对有虚函数的类生效,因为类型信息存在虚函数表附近;对非多态类型做dynamic_cast编译直接报错。滥用dynamic_cast通常意味着设计上可以用虚函数替代。
5. const_cast
const_cast 只能添加或去除 const(以及 volatile)限定,不能改变类型本身:
#include <cstring>
#include <iostream>
void print_len(const char* s)
{
std::cout << strlen(s) << std::endl;
}
int main()
{
char buf[] = "hello";
const char* cp = buf; // 对象本身不是 const,只是通过 const 指针看它
print_len(cp);
char* p = const_cast<char*>(cp); // 去掉 const,安全:buf 本来就可写
p[0] = 'H';
print_len(buf); // 输出 5(内容变成 Hello)
}
安全性取决于对象本身是否真的是 const:
- 对象原本不是 const(只是通过 const 指针/引用访问):去 const 后修改是安全的
- 对象本身就是 const:
const_cast后修改它是未定义行为,可能直接崩溃或悄悄出错
const int a = 10;
int* p = const_cast<int*>(&a);
*p = 20; // 未定义行为!a 是真正的 const 对象,编译器可能把 a 放在只读存储
const_cast 性能零开销,但它破坏类型系统的约束,属于"危险工具"。它最常见的正当用途是调用接口不友好(参数少了 const)的第三方库函数。
6. reinterpret_cast
reinterpret_cast 对内存中的位模式做位级重解释(bitwise reinterpretation),不进行任何检查,也不调整值——只是让编译器把同一块内存当成另一种类型来看:
#include <iostream>
#include <cstdint>
int main()
{
int i = 0x3f800000;
float f = reinterpret_cast<float&>(i); // 把 int 的位模式当作 float 解读,f == 1.0f
int n = 42;
uintptr_t addr = reinterpret_cast<uintptr_t>(&n); // 指针与整数互转
std::cout << f << ' ' << std::hex << addr << std::endl;
}
0x3f800000 按 IEEE 754 解读正好是 1.0f——同样的 4 个字节,换个视角就是另一个数。
安全性分析:
- 安全性最低:结果依赖平台对类型布局、大小端、对齐的假设,可移植性差;对齐不满足时解引用可能是未定义行为
- 性能:零开销,不生成任何指令
- 可读性:意图非常明确(“我要重解释这些位”),比 C 风格转换的隐式重解释更清晰
它只适合底层场景:硬件寄存器访问、网络协议解析、序列化、指针与整数互转等。
7. 四种 cast 的对比与选择
从安全性、性能、可读性三个维度整体对比:
| 转换方式 | 典型用途 | 安全性 | 运行时开销 | 可读性 |
|---|---|---|---|---|
| 隐式转换 | 数值提升、向上转型 | 中等(有符号/无符号陷阱) | 零 | 高 |
| C 风格转换 | 旧代码兼容 | 低 | 零 | 低(意图不明) |
static_cast |
相关类型转换、void* 还原 |
较高(编译期检查) | 零 | 高 |
dynamic_cast |
多态向下转型 | 高(运行时检查) | 有(RTTI 查询) | 高 |
const_cast |
去 const |
低(可能未定义行为) | 零 | 高 |
reinterpret_cast |
位级重解释、指针与整数互转 | 最低 | 零 | 高 |
选择原则:
- 能靠隐式转换解决的场景(数值提升、向上转型)直接用,不需要写 cast
- 需要显式转换时,默认选
static_cast——编译期检查 + 零开销 + 意图明确 - 多态向下转型且不确定实际类型,用
dynamic_cast,并接受它的运行时开销 - 不要写 C 风格转换,它把几种语义混在一起,无法表达意图
const_cast与reinterpret_cast是危险工具,只在底层或兼容场景使用,并且要用注释说明为什么必须这么做


