章节目录

显式类型转换和static_cast

本节阅读量:

在前面隐式类型转换中,我们讨论了编译器可以通过一个名为隐式类型转化的系统将值从一种数据类型隐式转换为另一种。当您希望以数字方式将值从一种数据类型提升为更宽的数据类型时,使用隐式类型转换是可以的。

许多新的C++程序员都会这样尝试:

1
double d = 10 / 4; // 其实是整数除法, d 的初始值是 2.0

由于10和4都是int类型,因此执行整数除法,表达式的计算结果为int值2。然后,在用于初始化变量d之前,将该值进行数值转换为double值2.0。很可能,这个结果不是预期的。

在使用字面值操作数的情况下,将一个或两个整数字面值替换为double类型将产生浮点数除法:

1
double d = 10.0 / 4.0; // 浮点数除法, d的初始值是 2.5

但是,如果您使用变量而不是字面值,该怎么办?考虑这种情况:

1
2
3
int x { 10 };
int y { 4 };
double d = x / y; // 其实是整数除法, d 的初始值是 2.0

因为这里使用整数除法,所以变量d的值将为2.0。在这种情况下,我们如何告诉编译器想使用浮点除法而不是整数除法?字面值后缀不能与变量一起使用。我们需要某种方法将一个(或两个)变量操作数转换为浮点类型,以便改用浮点除法。

幸运的是,C++附带了许多不同的类型转换操作符,程序员可以使用这些操作符来请求编译器执行类型转换。由于类型转换是程序员的显式请求,因此这种形式的类型转换通常称为显式类型转换(与隐式类型转换相反,后者由编译器自动执行类型转换)。


类型转换

C++支持5种不同类型的强制转换:C样式转换、静态转换、常量转换、动态转换和重解释转换。后四种有时被称为命名转换。

在本节中,我们将介绍C样式转换和静态转换。

通常应避免Const转换和重解释强制转换,因为它们仅在极少数情况下有用,并且如果使用不当,则可能有害。


C样式转换

在标准C编程中,类型转换是通过 () 操作符完成的,类型的名称放在括号中,对应的值在括号后。您仍然可以在从C代码传承而来的程序中看到这些。

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <iostream>

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

    
    double d { (double)x / y }; // 将 x 转换为 double ,以便执行浮点数除法
    std::cout << d << '\n'; // 打印 2.5

    return 0;
}

在上面的程序中,我们使用C样式转换来告诉编译器将x转换为double。由于运算符/的左操作数现在求值为浮点值,因此右操作数也将转换为浮点值。除法将使用浮点除法而不是整数除法!

C++还允许您使用具有更类似于函数调用的语法的C样式转换:

1
    double d { double(x) / y }; // 将 x 转换为 double ,以便执行浮点数除法

这与前面的示例执行相同,但将要转换的值括起来,更容易知道要转换的内容。

尽管C样式转换看起来是单个转换,但它实际上可以根据上下文执行各种不同的转换。这可以包括静态转换、常量转换或重解释转换(我们上面提到的后两种转换应该避免)。因此,C风格的类型转换有被无意中误用的风险,并且不会产生预期的行为,这可以通过使用C++类型转换来轻松避免。

此外,由于C样式转换只是类型名、括号和变量或值,因此它们既很难识别(使代码更难阅读),也更难搜索。


静态转换(static_cast)

C++引入了一个名为static_cast的转换操作符,可用于将一种类型的值转换为另一种类型。

您以前看到过static_cast用于将char转换为int,以便std::cout将其打印为整数,而不是char:

1
2
3
4
5
6
7
8
9
#include <iostream>

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

    return 0;
}

static_cast运算符接受表达式作为输入,并返回转换为尖括号内指定类型的计算值。static_cast最好用于将一种基本类型转换为另一种。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <iostream>

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

    // static cast 将 x 转换为 double,以便执行浮点数除法
    double d { static_cast<double>(x) / y };  
    std::cout << d << '\n'; // 打印 2.5

    return 0;
}

static_cast的主要优势是它提供了编译时类型检查,从而更难发生意外错误。

1
2
    // 一个 C样式的字面值,不能被转换为int,所以下面是一个非法的转换
    int x { static_cast<int>("Hello") }; // 无效: 编译失败

static_cast也(有意地)不如C样式转换强大,因此您不能无意中删除const,或做其他您可能不想做的事情。

1
2
3
4
5
6
7
8
int main()
{
	const int x{ 5 };
	int& ref{ static_cast<int&>(x) }; // 无效: 编译失败
	ref = 6;

	return 0;
}

使用static_cast使窄化转换显式

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

1
2
int i { 48 };
char ch = i; // 隐式的窄化转换

将int(2或4个字节)强制转换为char(1个字节)可能是不安全的(因为编译器无法判断整数值是否会溢出char的范围),因此编译器通常会打印警告。如果使用列表初始化,编译器将报错。

为了解决这个问题,我们可以使用静态转换来显式地将整数转换为字符:

1
2
3
4
int i { 48 };

// 显示的将 int 转换为 char, 以便转换后的结果可以赋值给ch
char ch { static_cast<char>(i) };

当我们这样做时,显式地告诉编译器这个转换是有意的,并且我们对后果负责(例如,转换前的数字可能溢出char的范围)。由于该static_cast的输出是char类型,因此变量ch的初始化不会有任何类型不匹配,因此不会出现警告或错误。

下面是另一个编译器通常告警将double转换为int可能会导致数据丢失的示例:

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

要告诉编译器我们明确打算这样做:

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

10.4 算术转换

上一节

10.6 typedef和类型别名

下一节