数值提升
本节阅读量:在前面,讲解对象大小和sizeof运算符中,我们注意到C++对每个基本类型都有最小大小保证。然而,这些类型的实际大小可能因编译器和体系结构而异。
允许这种可变性,以便将int和double数据类型设置为在给定架构上最大化性能的大小。例如,32位计算机通常一次能够处理32位数据。在这种情况下,int可能被设置为32位,因为这是CPU操作的数据的“自然”大小(并且可能是最具性能的)。
但当我们希望32位CPU修改8位值(如char)或16位值时,会发生什么情况?某些32位处理器(如32位x86 CPU)可以直接操作8位或16位值。然而,这样做通常比操作32位值慢!其他32位CPU(如32位PowerPC CPU)只能在32位值上操作,必须使用其他技巧来操作较窄的值。
一个提醒
数据类型使用的位数称为其宽度。更宽的数据类型是使用更多比特的数据类型,而更窄的数据类型则是使用更少比特的数据。
数值提升
由于C++被设计为在广泛的体系结构中具有可移植性和高性能,因此语言设计者不希望假设,给定的CPU能够有效地处理比该CPU的自然数据更窄的值。
为了帮助解决这一挑战,C++定义了一类类型转换,非正式地称为数值提升。数值提升是将某些较窄的数值类型(如char)转换为某些较宽的数值类型的类型转换(通常为int或double),这些类型可以有效地处理,并且不太可能产生溢出的结果。
所有数值提升都是保值的,这意味着转换的值将始终等于原值(尽管它将具有不同的类型)。由于源类型的所有值都可以在目标类型中精确表示,因此保留值的转换被称为“安全转换”。
由于提升是安全的,编译器将根据需要自由使用数值提升,并且在这样做时不会发出警告。
数值提升减少冗余
数值提升也解决了另一个问题。考虑您想要编写一个函数来打印int类型值的情况:
|
|
虽然这很简单,但如果我们希望也能够打印short或char类型的值,会发生什么?如果不存在类型转换,我们将不得不编写不同的打印函数,为short和char编写另外的打印函数。同时不要忘记unsigned char、signed char、unsigned short、wchar_t、char8_t、char16_t和char32_t的另外版本!可以看到,这是如何迅速变得无法管理。
这里,数值提升起了解救作用:我们可以编写具有int和double参数的函数(例如上面的printInt()函数)。然后,可以使用较窄类型的参数调用相同的代码,这些参数可以通过数值提升来匹配函数参数的类型。
数值提升类别
数值提升规则分为两个子类别:整型提升和浮点提升。只有这些类别中列出的转换才被视为数值提升。
浮点提升
我们从容易的开始。
使用浮点提升规则,可以将float类型的值转换为double类型的值。
这意味着我们可以编写一个接受double的函数,然后用double或float值调用它:
|
|
在对 printDouble() 的第二次调用中,浮点字面值 4.0f 被提升为double,以便传递的值与函数参数的类型匹配。
整形提升
整形提升规则更复杂。
使用整形提升规则,可以进行以下转换:
- 有符号char或有符号short可以转换为int。
- 如果int可以保存对应类型的整个范围,则无符号char、char8_t和unsigned short可以转换为int,否则转换为unsigned int。
- 如果默认情况下char是有符号的,则它遵循上面的有符号char转换规则。如果默认情况下它是无符号的,则遵循上面的无符号字符转换规则。
- bool可以转换为int,false变为0,true变为1。
假设int具有4个字节或更大的字节数(这是当今的典型情况),上面的内容基本上意味着bool、char、signed char、unsigned char、signed short和unsigned short都被提升为int。
还有一些其他整形提升规则使用得较少。这些可以在以下位置找到:https://en.cppreference.com/w/cpp/language/implicit_conversion#Integral_promotion 。
在大多数情况下,这允许我们编写一个带int参数的函数,然后将其与各种其他整形类型一起使用。例如:
|
|
这里有两件事值得注意。首先,在某些架构(例如,int具有2字节)上,某些无符号整数类型会提升为unsigned int,而不是int。
其次,一些较窄的无符号类型(如无符号字符)可以升级为较大的有符号类型(例如int)。因此,虽然整数提升是保值的,但它不一定保留类型的符号(有符号/无符号)。
并非所有扩大转换都是数值提升
一些扩展类型转换(如char到short或int到long)在C++中不被视为数值提升(它们是数字转换,后面介绍)。这是因为这样的转换无助于将较小的类型转换为可以更有效地处理的较大类型。
这里的区别主要是学术性的。然而,在某些情况下,编译器更喜欢数值提升,而不是数字转换。我们将看到在讨论函数重载解决方案时这会产生影响的示例。
