Skip to content

8.5 - 显式类型转换和static_cast

Key Takeaway

在 8.1 - 隐式类型转换中我们介绍过,编译器可以隐式地将一种类型的值转换成另外一种类型,即隐式类型转换。当你想要将一个数值类型通过数值提升的方式转换为更宽的类型时,使用隐式类型转换是可以的。

很多新手 C++ 程序员会这样做:

1
double d = 10 / 4; // 通过整型乘法,将值初始化为 2.0

因为 10 和 4 都是 int类型,所以执行的是整型除法,表达式的求值结果为 int 值 2。然后,该值在被用于初始化变量 d 之前被转换成了 double 类型的 2.0 。 多数情况下,程序员并不会故意这么做。

下面这个例子中,我们使用了字面量,将上面的int型操作数替换成了double操作数,这样一来就会进行浮点数除法:

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

但是,如果你使用的是变量而非字面量呢?考虑下面的例子:

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

因为执行了整型除法,所以变量 d的值最终为2.0。那么我们应该如何告诉编译器,这里需要使用浮点数除法呢?字面量后缀并不能被用在变量上。因此,需要一种能够将变量转换为浮点类型的方法,以便使用浮点数除法。

幸运的是,C++提供了许多不同的类型转换操作符(通常称为类型转换),程序员可以使用它们请求编译器执行类型转换。因为类型转换是程序员的显式请求,所以这种形式的类型转换通常称为显式类型转换(与隐式类型转换相反,隐式类型转换是编译器自动执行的类型转换)。

类型转换

C++支持 5 种类型的显示类型转换: C风格类型转换静态类型转换const 类型转换动态类型转换重新解释类型转换。后四种类型有时称为具名名类型转换(named cast)

在本课中,我们将介绍C风格类型转换静态类型转换

相关内容

我们会在18.10 -- Dynamic casting中介绍动态类型转换,但是我们首先要介绍一些前置内容。

通常应该避免使用const 类型转换重新解释类型转换,因为只有很少的情况下会需要使用它们,而且使用不当是非常有害的。

注意

除非你有充分的理由,否则请避免使用const 类型转换重新解释类型转换

C语言风格的类型转换

在标准的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 }; // convert x to a double so we get floating point division
    std::cout << d; // prints 2.5

    return 0;
}

在上面的程序中,我们使用了一个C语言风格的类型转换,要求编译器将x转换为double。因为,/左侧的操作数被转换成了浮点数,所以操作符右侧的数同样也会被转换为浮点数(数值转换),然后表达式就会按照浮点数除法而不是整型除法求值!

C++ 允许你使用一种更加类似函数调用的语法来使用C语言风格的类型转换:

1
double d { double(x) / y }; // convert x to a double so we get floating point division

这种方式实现的类型转换和之前一种是完全一样的,但是将需要转换的变量放在括号了,更容易看清楚被转换的对象是什么。

虽然“c风格类型转换”看起来是单一类型转换,但实际上它可以根据上下文执行各种不同的转换。这可以包括“静态类型转换”、“const类型转换”或“重新解释类型转换”(我们在上面提到的后两种类型应该避免)。因此,“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'; // prints a 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 to a double so we get floating point division
    double d { static_cast<double>(x) / y };
    std::cout << d; // prints 2.5

    return 0;
}

static_cast 最大的优势是它提供了运行时的类型检查机制,这样就不容易犯下由粗心导致的问题。 static_cast 的功能不如 C 语言风格的类型转换(故意的)强大,所以你不会无意间移除const或其他你本不希望发生的事情。

最佳实践

在需要进行类型转换时,优先使用 static_cast

使用 static_cast 进行显式地缩窄转换

当我们进行具有潜在危险的(缩窄)隐式类型转换时,编译器通常会发出告警。例如,考虑下面这段代码:

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

int (2 字节或者 4 字节) 转换为char (1 字节)通常是不安全的(因为编译器无法判断整型值是否会超出char能够表示的范围),因此编译器通常会产生警告。而如果我们使用的是列表初始化括号初始化),编译器则通常会产生一个编译错误。(参见:括号初始化不允许隐式缩窄转换)

为了避免这些问题,我们可以使用 static_cast 显式地将整型转换为 char

1
2
3
4
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 的范围)。由于这个 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);