在 隐式类型转换 中,我们讨论了编译器如何通过隐式类型转换把一个值从一种数据类型转换为另一种。当需要把值数值提升到更宽的数据类型时,使用隐式转换即可。
许多 C++ 新手会写出如下代码:
double d = 10 / 4; // 执行整数除法,d 被初始化为 2.0
由于 10
和 4
都是 int
,执行整数除法,结果为 int
类型的 2
,再经数值转换变为 double
的 2.0
。这通常并非作者本意。
若使用字面量,把其中一个或两个整数改为浮点字面量即可触发浮点除法:
double d = 10.0 / 4.0; // 浮点除法,d 被初始化为 2.5
但如果操作数是变量呢?
int x{ 10 };
int y{ 4 };
double d = x / y; // 整数除法,d 为 2.0
此时无法使用字面量后缀。我们需要把某个(或两个)操作数显式转换为浮点类型,以触发浮点除法。
C++ 提供了多种强制转换运算符(cast operator,简称 cast),供程序员显式要求编译器执行类型转换,这种形式称为显式类型转换(explicit type conversion),与编译器自动完成的隐式类型转换相对。
强制转换的种类
C++ 支持 5 种 cast:
static_cast
dynamic_cast
const_cast
reinterpret_cast
- C 风格 cast
前四种合称命名 cast(named casts)。
供进阶读者参考
Cast | 描述 | 是否安全 |
---|---|---|
static_cast | 在相关类型之间执行编译期类型转换 | 是 |
dynamic_cast | 在运行时对多态继承层次中的指针或引用进行类型转换 | 是 |
const_cast | 添加或移除 const 限定符 | 仅添加 const 时安全 |
reinterpret_cast | 按位重解释,把一种类型的二进制模式视为另一种类型 | 否 |
C 风格 cast | 可能组合 static_cast 、const_cast 、reinterpret_cast 等行为 | 否 |
所有 cast 的用法一致:输入一个表达式(求值后得到值或对象)以及目标类型,输出转换后的结果。
由于 static_cast
与 C 风格 cast 最常见,本课重点讨论这两者。
相关内容
dynamic_cast
将在 25.10 课 —— 动态强制转换 中介绍(需先掌握继承等前置知识)。const_cast
与reinterpret_cast
应尽量避免,仅在最必要时使用。
警告
除非有充分理由,否则不要使用 const_cast
与 reinterpret_cast
。
C 风格 cast
在标准 C 中,强制转换使用 ()
运算符,括号内写目标类型,括号右侧写待转换值。C++ 中称为 C 风格 cast,在旧 C 代码中仍可见:
#include <iostream>
int main()
{
int x{ 10 };
int y{ 4 };
std::cout << (double)x / y << '
'; // C 风格 cast
return 0;
}
C++ 还提供另一种 C 风格形式——函数风格 cast,语法类似函数调用:
std::cout << double(x) / y << '
'; // 函数风格 cast
避免 C 风格 cast 的原因
- 功能不透明:一个 C 风格 cast 可能实际执行
static_cast
、const_cast
、reinterpret_cast
或其组合,语义不明确,易误用。 - 可读性差:仅由类型名、括号、变量组成,难以识别与搜索。
相比之下,命名 cast 易于识别、语义单一,误用时会直接产生编译错误。
最佳实践
避免使用 C 风格 cast。
供进阶读者参考
C 风格 cast 依次尝试:
const_cast
static_cast
static_cast
+const_cast
reinterpret_cast
reinterpret_cast
+const_cast
C 风格 cast 独有功能:可将派生类对象转换为不可访问的基类(如私有继承的基类),而 C++ cast 无法做到。
static_cast:最常用的 cast
static_cast
是最常用的强制转换运算符,用于显式地把某类型值转换为另一类型。
之前我们用它把 char
转为 int
,使 std::cout
按整数输出:
#include <iostream>
int main()
{
char c{ 'a' };
std::cout << static_cast<int>(c) << '
'; // 输出 97
return 0;
}
语法:static_cast<目标类型>(表达式)
,形似函数调用,返回一个直接初始化的临时对象。
用 static_cast
解决引言中的问题:
#include <iostream>
int main()
{
int x{ 10 };
int y{ 4 };
std::cout << static_cast<double>(x) / y << '
'; // 输出 2.5
return 0;
}
static_cast<double>(x)
生成临时 double
值 10.0
,随后执行浮点除法。
static_cast
的两项关键特性
- 编译期类型检查:若转换非法,编译时报错。
int x{ static_cast<int>("Hello") }; // 非法:字符串字面量无法转 int
- 能力受限:不会执行
reinterpret_cast
或丢弃const
等危险操作。
最佳实践
需要显式类型转换时,优先使用 static_cast
。
供进阶读者参考
static_cast
使用直接初始化,因此目标类型的显式构造函数也会被考虑(见 14.16 课 —— 转换构造函数与 explicit 关键字)。
使用 static_cast 显式窄化转换
编译器常对潜在不安全的窄化隐式转换发出警告。例如:
int i{ 48 };
char ch = i; // 隐式窄化,编译器警告
可用 static_cast
显式化:
int i{ 48 };
char ch{ static_cast<char>(i) }; // 显式转换,无警告
再一例:
int i{ 100 };
i = i / 2.5; // 编译器警告:double→int 可能丢失数据
i = static_cast<int>(i / 2.5); // 显式转换,无警告
相关内容
更多 static_cast
在类类型中的应用见 14.13 课 —— 临时类对象。
cast 与临时对象初始化的区别
若需把变量 x
转为 int
,有两种惯用写法:
static_cast<int>(x)
—— 返回按int
直接初始化的临时对象。int{ x }
—— 返回按int
直接列表初始化的临时对象。
应避免int(x)
,这是 C 风格 cast,存在前述缺点。
static_cast
与列表初始化临时对象的三点主要差异:
- 窄化检查:
int{ x }
禁止窄化;static_cast
允许窄化。 - 可读性:
static_cast
更显式,易于搜索。 - 语法限制:列表初始化临时对象仅允许单字类型名(simple type specifier),如
int{ x }
合法,unsigned int{ x }
非法。
示例:
int main()
{
unsigned char c{ 'a' };
// std::cout << unsigned int{ c }; // 错误:非单字类型
using uint = unsigned int;
std::cout << uint{ c }; // OK,但不如 static_cast
return 0;
}
综上,我们更倾向于使用 static_cast
。
最佳实践
需要转换时,优先使用 static_cast
,而非列表初始化临时对象。
小测验
问题 #1
隐式类型转换与显式类型转换有何区别?
(参考答案略)