显式类型转换(强制转换)与 static_cast

在 隐式类型转换 中,我们讨论了编译器如何通过隐式类型转换把一个值从一种数据类型转换为另一种。当需要把值数值提升到更宽的数据类型时,使用隐式转换即可。

许多 C++ 新手会写出如下代码:

double d = 10 / 4;   // 执行整数除法,d 被初始化为 2.0

由于 104 都是 int,执行整数除法,结果为 int 类型的 2,再经数值转换变为 double2.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_castconst_castreinterpret_cast 等行为

所有 cast 的用法一致:输入一个表达式(求值后得到值或对象)以及目标类型,输出转换后的结果。

由于 static_cast 与 C 风格 cast 最常见,本课重点讨论这两者。

相关内容

  • dynamic_cast 将在 25.10 课 —— 动态强制转换 中介绍(需先掌握继承等前置知识)。
  • const_castreinterpret_cast 应尽量避免,仅在最必要时使用。

警告

除非有充分理由,否则不要使用 const_castreinterpret_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 的原因

  1. 功能不透明:一个 C 风格 cast 可能实际执行 static_castconst_castreinterpret_cast 或其组合,语义不明确,易误用。
  2. 可读性差:仅由类型名、括号、变量组成,难以识别与搜索。

相比之下,命名 cast 易于识别、语义单一,误用时会直接产生编译错误。

最佳实践

避免使用 C 风格 cast。

供进阶读者参考

C 风格 cast 依次尝试:

  1. const_cast
  2. static_cast
  3. static_cast + const_cast
  4. reinterpret_cast
  5. 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) 生成临时 double10.0,随后执行浮点除法。

static_cast 的两项关键特性

  1. 编译期类型检查:若转换非法,编译时报错。
    int x{ static_cast<int>("Hello") };   // 非法:字符串字面量无法转 int
    
  2. 能力受限:不会执行 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,有两种惯用写法:

  1. static_cast<int>(x) —— 返回按 int 直接初始化的临时对象。
  2. int{ x } —— 返回按 int 直接列表初始化的临时对象。
    应避免 int(x),这是 C 风格 cast,存在前述缺点。

static_cast 与列表初始化临时对象的三点主要差异:

  1. 窄化检查int{ x } 禁止窄化;static_cast 允许窄化。
  2. 可读性static_cast 更显式,易于搜索。
  3. 语法限制:列表初始化临时对象仅允许单字类型名(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

隐式类型转换与显式类型转换有何区别?

(参考答案略)

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"