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

第10.1课——隐式类型转换中,我们讨论过编译器可通过隐式类型转换将值从一种数据类型转换为另一种。当需要将值从某数据类型提升为更宽数据类型时,使用隐式转换是可行的。

许多C++新手会尝试如下写法:

double d = 10 / 4; // does integer division, initializes d with value 2.0

由于10和4同为int类型,系统执行整数除法,表达式结果为int值2。该值随后经数值转换为double值2.0,最终用于初始化变量d。这通常并非开发者本意。

当使用字面量操作数时,若将一个或两个整数字面量替换为双精度字面量,则会触发浮点除法:

double d = 10.0 / 4.0; // does floating point division, initializes d with value 2.5

但若使用变量而非字面量呢?考虑以下情况:

int x { 10 };
int y { 4 };
double d = x / y; // does integer division, initializes d with value 2.0

由于此处采用整数除法,变量d最终将被赋值为2.0。如何告知编译器在此情况下使用浮点除法而非整数除法?变量无法使用字面量后缀。我们需要某种方法将一个(或两个)变量操作数转换为浮点类型,从而触发浮点除法。

幸运的是,C++提供了多种类型转换运算符type casting operators(通常称为强制转换casts),程序员可借此让编译器执行类型转换。由于强制转换是程序员的显式请求,这种转换常被称为显式类型转换explicit type conversion(相对于编译器自动执行的隐式类型转换)。


类型转换

C++支持5种不同类型的转换:static_cast、dynamic_cast、const_cast、reinterpret_cast以及C风格转换。前四种有时被称为命名转换。

对于进阶读者

Cast / 转换符 Description / 描述 Safe? / 安全?
static_cast
静态强制类型转换
Performs compile-time type conversions between related types.
在相关类型间执行编译时类型转换
Yes
dynamic_cast
动态强制类型转换
Performs runtime type conversions on pointers or references in an polymorphic (inheritance) hierarchy
在多态(继承)层次结构中对指针或引用执行运行时类型转换
Yes
const_cast
常量强制类型转换
Adds or removes const.
添加或移除 const 修饰符
Only for adding const
仅用于添加 const
reinterpret_cast
重解释强制类型转换
Reinterprets the bit-level representation of one type as if it were another type
将一种类型的位级表示重新解释为另一种类型
No
C-style casts
C 风格强制类型转换
Performs some combination of static_cast, const_cast, or reinterpret_cast.
组合使用 static_cast、const_cast 或 reinterpret_cast 实现特定转换
No

每种转换的运作原理相同 转换操作以表达式(求值为值或对象)和目标类型作为输入,返回转换结果。

由于C风格转换是最常用的类型转换,本节将重点讲解C风格转换和静态强制类型转换static_cast

相关知识
动态转换将在第25.10节——动态转换中讲解,该节内容需在完成其他先修知识后学习。

const_cast 和 reinterpret_cast 通常应避免使用,因为它们仅在极少数情况下有用,且使用不当可能造成危害。

警告
除非有充分理由,否则请避免使用 const_cast 和 reinterpret_cast。


C 风格强制转换

在标准 C 编程中,强制转换通过 operator() 实现:括号内指定目标类型名称,右括号紧邻处放置待转换值。在 C++ 中,此类转换称为 C 风格强制转换C-style cast。从 C 转换而来的代码中仍可能见到此用法。

例如:

#include <iostream>

int main()
{
    int x { 10 };
    int y { 4 };

    std::cout << (double)x / y << '\n'; // C-style cast of x to double

    return 0;
}

image

在上例程序中,我们使用C式强制转换告知编译器将x转换为double类型。由于operator/运算符的左操作数现已求值为浮点值,右操作数也将被转换为浮点值,从而使除法运算采用浮点除法而非整数除法。

C++还提供了一种替代形式的C式强制转换,即函数式强制转换function-style cast,其形式类似函数调用:

std::cout << double(x) / y << '\n'; //  // function-style cast of x to double

函数式强制类型转换能更清晰地表明转换对象(其形式类似标准函数实参)

现代C++中通常避免使用C式强制类型转换有两个重要原因:

首先,尽管C式强制类型转换看似单次转换,但实际可能执行多种不同转换,具体取决于使用方式。这可能包含静态转换、常量转换或重新解释转换(后两者如前所述应避免使用)。C 风格强制类型转换无法明确实际执行的转换类型,这不仅使代码难以理解,更可能导致无意中的误用(以为执行了简单转换,实则引发危险操作)。此类错误往往在运行时才暴露。

此外,由于C式强制转换仅由类型名、括号及变量/值构成,其不仅难以识别(降低代码可读性),更难以检索。

相比之下,命名强制类型转换易于识别和检索,能清晰表明其作用,功能受限,且若被误用将引发编译错误。

最佳实践
避免使用C风格强制类型转换。

对于进阶读者

C 风格的强制转换尝试按以下顺序执行下列 C++ 强制转换:

  • const_cast
  • static_cast
  • static_cast,随后是 const_cast
  • reinterpret_cast
  • reinterpret_cast,随后使用const_cast

C风格强制转换能实现C++强制转换无法做到的一点:它能将派生对象转换为无法访问的基类(例如因私有继承导致)。


应使用static_cast对大多数值进行类型转换

迄今为止,C++中最常用的类型转换是静态转换static cast运算符,通过static_cast关键字调用。当需要显式将某种类型的值转换为另一种类型的值时,便会使用static_cast。

你之前曾见过static_cast用于将char转换为int,以便std::cout将其作为整数而非字符打印:

#include <iostream>

int main()
{
    char c { 'a' };
    std::cout << static_cast<int>(c) << '\n'; // prints 97 rather than a

    return 0;
}

要执行静态转换(static cast),我们首先使用 static_cast(静态强制类型转换) 关键字,然后将要转换的目标类型放在尖括号内。接着在圆括号中放置待转换值的表达式。请注意其语法多么类似于对名为 static_cast<type>() 的函数调用——其中待转换值的表达式作为参数传递!将值静态转换为另一种类型时,会返回一个临时对象,该对象通过转换后的值进行直接初始化。

以下是使用 static_cast 解决本课开篇提出的问题的方法:

#include <iostream>

int main()
{
    int x { 10 };
    int y { 4 };

    // static cast x to a double so we get floating point division
    std::cout << static_cast<double>(x) / y << '\n'; // prints 2.5

    return 0;
}

static_cast(x) 返回一个包含转换后值 10.0 的临时 double 对象。该临时对象随后被用作浮点除法的左操作数。

static_cast 具有两个重要特性:

首先,static_cast 提供了编译时类型检查。如果尝试将某个值转换为某种类型,而编译器不知道如何执行该转换,就会出现编译错误。

// a C-style string literal can't be converted to an int, so the following is an invalid conversion
int x { static_cast<int>("Hello") }; // invalid: will produce compilation error

image

其次,static_cast 的功能(有意)弱于 C 风格的强制转换,因为它会阻止某些危险的转换(例如需要重新解释或丢弃 const 约束的转换)。

最佳实践
当需要将值从一种类型转换为另一种类型时,优先使用 static_cast。

对于进阶读者
由于 static_cast 使用直接初始化,在初始化待返回的临时对象时,目标类型的任何显式构造函数都会被考虑。我们在第 14.16 节——转换构造函数与显式关键字中讨论了显式构造函数。


使用 static_cast 显式进行窄化转换

当执行潜在不安全的(窄化)隐式类型转换时,编译器通常会发出警告。例如,考虑以下代码片段:

int i { 48 };
char ch = i; // implicit narrowing conversion

image

将 int(2或4字节)强制转换为 char(1字节)存在潜在风险(编译器无法判断整数值是否会溢出 char 的范围),因此编译器通常会输出警告。若使用列表初始化,编译器则会报错。

为解决此问题,我们可以使用静态强制转换将整数显式转换为字符:

int i { 48 };

// explicit conversion from int to char, so that a char is assigned to variable ch
char ch { static_cast<char>(i) };

这样做时,我们明确告知编译器此转换是有意的,并承担由此产生的后果(例如字符类型溢出)。由于静态转换的输出类型为 char,变量 ch 的初始化不会引发类型不匹配,因此不会产生警告或错误。

这里还有一个例子,编译器通常会警告说将双精度浮点数转换为整数可能会导致数据丢失:

int i { 100 };
i = i / 2.5;

image

向编译器明确告知我们有意执行此操作:

int i { 100 };
i = static_cast<int>(i / 2.5);

image

相关内容
我们在第14.13节——临时类对象中,将进一步探讨static_cast在类类型中的应用场景。


临时对象的强制转换与初始化

假设我们有一个需要转换为整数的变量 x。实现此转换有两种常规方法:

  1. static_cast<int>(x),该方法返回一个用直接初始化direct-initialized x 的临时 int 对象。
  2. int { x },该方法创建一个直接用列表初始化direct-list-initialized x 的临时 int 对象。

我们应避免使用 int(x) 这种 C 风格的强制转换。它会返回一个直接用 x 的值初始化的临时 int(正如语法所示),但同时也存在 C 风格强制转换部分提到的其他弊端(例如允许执行危险的转换)。

static_cast 与直接列表初始化临时变量存在(至少)三点显著差异:

  1. int { x } 使用列表初始化,禁止窄化转换。这在初始化变量时非常理想,因为此类场景通常不希望丢失数据。但使用强制转换时,系统默认开发者清楚操作意图。若需执行可能丢失数据的转换,理应允许操作。此时缩窄转换限制反而成为障碍。

让我们举个例子说明这一点,包括它如何导致平台特有的问题:

#include <iostream>

int main()
{
    int x { 10 };
    int y { 4 };

    // We want to do floating point division, so one of the operands needs to be a floating point type
    std::cout << double{x} / y << '\n'; // okay if int is 32-bit, narrowing if x is 64-bit
}

image

在此示例中,我们决定将x转换为双精度浮点数,以便执行浮点除法而非整数除法。在32位架构上,这种转换完全可行(因为双精度浮点数能表示所有可存储于32位整数中的值,因此不属于窄化转换)。但在64位架构上情况不同,此时将64位整数转换为双精度浮点数属于窄化转换。由于列表初始化禁止窄化转换,在整型为64位的架构上该代码将无法编译。

  1. static_cast能更明确地表明我们有意进行转换。尽管static_cast比直接列表初始化更冗长,但在这种情况下这是好事——它使转换更易于识别和检索,最终让代码更安全、更易理解。

  2. 临时变量的直接列表初始化仅允许单词类型的名称。由于语法上的特殊限制,C++ 中存在若干仅允许单词类型名称的场景(C++ 标准将此类名称称为“简单类型指定符simple type specifiers”)。因此,虽然 int { x } 是有效的转换语法,但 unsigned int { x } 则不可行。

您可以在以下示例中亲眼看到这一点,该示例会引发编译错误:

#include <iostream>

int main()
{
    unsigned char c { 'a' };
    std::cout << unsigned int { c } << '\n';

    return 0;
}

image

存在一些简单的解决方法,其中最简单的是使用单词类型的别名:

#include <iostream>

int main()
{
    unsigned char c { 'a' };
    using uint = unsigned int;
    std::cout << uint { c } << '\n';

    return 0;
}

image

既然可以直接用static_cast,何必大费周章呢?

基于以上原因,我们通常更倾向于使用static_cast而非直接列表初始化临时对象。

最佳实践
需要进行类型转换时,优先使用static_cast而非初始化临时对象。


测验时间

问题 #1

隐式类型转换和显式类型转换有什么区别?

显示答案

当预期某一数据类型时,但实际提供的是不同数据类型,编译器会自动执行隐式类型转换。

显式类型转换发生在程序员使用类型转换显式地将一个值从一种类型转换为另一种类型时。
posted @ 2026-03-03 19:06  游翔  阅读(0)  评论(0)    收藏  举报