类型转换和static_cast简介
本节阅读量:隐式类型转换
考虑以下程序:
|
|
在上面的示例中,print() 函数有一个类型为double的参数,但调用方传入的是int类型的值5。在这种情况下会发生什么?
在大多数情况下,C++允许我们将一种基础类型的值转换为另一种基础类型。将值从一种类型转换为另一种类型的过程称为类型转换。因此,int参数5将被转换为双精度值5.0,然后复制到参数x中。print() 函数将打印该值,产生以下输出:
|
|
编译器在未经我们明确要求的情况下进行的类型转换,称为隐式类型转换。正如上面的例子所示——我们没有明确地告诉编译器将整数值5转换为双精度值5.0。相反,函数需要一个双精度值,而我们传入了一个整数。编译器会注意到类型不匹配,并隐式地将整数转换为双精度浮点数。
下面是一个类似的示例,其中我们的参数是int变量,而不是int值:
|
|
这与上面的工作原理相同。int变量y保存的值(5)将被转换为双精度值5.0,然后复制到参数x中。
类型转换会生成新值
即使被称为转换,类型转换实际上并不会更改原始值或其类型。相反,要转换的值被用作输入,转换操作会产生一个目标类型的新值。
在上面的示例中,转换不会将变量y的类型从int更改为double。相反,转换以y的值(5)作为输入,创建一个新的双精度值(5.0),然后将该双精度值传递给函数print。
关键点
类型转换使用其它类型的值生成目标类型的新值。
隐式类型转换警告
虽然隐式类型转换在大多数情况下没有问题,但在少数情况下可能存在隐患。考虑下面的程序,它类似于上面的示例:
|
|
在这个程序中,我们将print() 更改为接受int参数,函数调用现在传入双精度值5.5。与上面类似,编译器将使用隐式类型转换将双精度值5.5转换为int类型的值,以便将其传递给函数print()。
与前面的示例不同,编译该程序时,编译器会生成关于可能丢失数据的警告。并且,如果您启用了”将警告视为错误”的选项,编译器将中止编译过程。
编译和运行时,此程序将打印以下内容:
|
|
请注意,尽管我们传入了值5.5,但程序打印了5。由于整数值不能保存小数部分,因此当双精度值5.5隐式转换为int时,小数部分会被丢弃,仅保留整数值。
由于将浮点值转换为整数值会导致小数部分被丢弃,因此编译器在执行从浮点到整数值的隐式类型转换时会发出警告。即使我们传入的浮点值不含小数部分,编译器仍然可能会警告该转换不安全。
关键点
某些类型转换始终是安全的(例如int到double),而另一些可能会导致值在转换过程中发生改变(例如double到int)。不安全的隐式转换通常会生成编译器警告,或(在列表初始化的情况下)产生错误。
这是列表初始化成为首选初始化形式的主要原因之一。列表初始化会确保不会因隐式类型转换而丢失值信息:
|
|
相关内容
隐式类型转换是一个内容丰富的主题。在以后的课程中,我们将更深入地探讨它。
通过static_cast操作符进行显式类型转换
回到我们最近的print()示例,如果我们故意想将双精度值传递给接受整数的函数,仅仅关闭”将警告视为错误”来让程序通过编译并不是好的做法,因为这样每次编译时都会有警告(我们很快就会习惯于忽略它),最终可能会忽视关于更严重问题的警告。
C++支持第二种类型转换方法,称为显式类型转换。显式类型转换允许我们(程序员)明确地告诉编译器将值从一种类型转换为另一种类型,并且我们对转换结果承担全部责任(这意味着,如果转换导致值的丢失,那是我们自己的责任)。
要执行显式类型转换,在大多数情况下我们会使用static_cast操作符。其语法看起来有点特别:
|
|
static_cast将表达式的值作为输入,并返回转换为newtype所指定类型的值(例如int、bool、char、double)。
让我们使用static_cast更新之前的程序:
|
|
因为我们现在显式地将双精度值5.5转换为int值,所以编译器不会生成关于可能丢失数据的警告(这意味着我们可以继续启用”将警告视为错误”)。
关键点
每当您看到使用尖括号(<>)的C++语法(不包括预处理器)时,尖括号之间的东西很可能是类型。这通常是C++处理需要参数化类型的代码的方式。
相关内容
C++支持其他类型的强制转换。在以后的课程中,我们将详细讨论不同类型的类型转换——显式类型转换(casting)和static_cast。
使用static_cast将char转换为int
在前面关于字符的课程中,我们看到使用std::cout打印char值时会将其输出为字符:
|
|
这将打印:
|
|
如果想打印对应的整数值而不是字符,可以使用static_cast将值从char转换为int:
|
|
这将打印:
|
|
值得注意的是,static_cast的参数是作为表达式求值的。当我们传入一个变量时,该变量会被求值以产生其值,然后该值被转换为新类型。变量本身不会受到类型转换的影响。在上面的例子中,变量ch仍然是char类型,即使在我们将其值转换为int之后,它仍然保持不变。
将无符号数字转换为有符号数字
要将无符号数字转换为有符号数字,也可以使用static_cast运算符:
|
|
static_cast操作符不执行任何范围检查,因此如果将值强制转换为超出其表示范围的类型,将导致未定义的行为。因此,如果无符号int的值大于有符号int能容纳的最大值,上述从无符号int到int的转换将产生不可预测的结果。
警告
如果要转换的值不适合新类型的范围,static_cast操作符将产生未定义的行为。
std::int8_t和std::uint8_t的行为可能类似于字符,而不是整数
如 固定宽度整数和size_t 中所述,大多数编译器将std::int8_t和std::uint8_t(以及相应的快速和最小固定宽度类型)分别定义和处理为有符号char和无符号char类型。现在我们已经了解了什么是字符,可以看看这在哪些地方会产生问题:
|
|
因为std::int8_t的名称中包含"int",您可能会误以为上面的程序会打印整数值65。然而,在大多数系统上,该程序实际上会打印A(将myInt当作字符处理)。不过,这个行为并不确定(在某些系统上,它确实可能打印65)。
如果希望确保std::int8_t或std::uint8_t对象被当作整数处理,可以使用static_cast将其值转换为整数:
|
|
当std::int8_t被视为字符时,从控制台读取输入也可能出现问题:
|
|
该程序的示例运行:
|
|
下面解释一下这里发生了什么。当std::int8_t被视为字符时,我们的输入会被当作字符而非整数来解释。所以当我们输入35时,实际上输入了两个字符'3’和'5’。因为char对象只能包含一个字符,所以只提取了'3’(‘5’留在输入流中,供后续可能的提取)。因为字符'3’的ASCII码是51,所以值51被存储在myInt中,然后我们将其作为int打印出来。
相比之下,其他固定宽度类型始终会按整数值进行打印和输入。