在对象大小与 sizeof 运算符 中,我们指出 C++ 对每种基本类型都规定了最小位宽,而其真实大小可随编译器及目标架构而异。
此灵活性旨在让 int
与 double
等类型能够被设定为在特定架构上性能最优的位宽。例如,32 位计算机通常一次可处理 32 位数据;此时,int
多半被设为 32 位,因为这正是该 CPU 的“自然”数据宽度(也往往是最快的情形)。
提示
数据类型所用的比特数称为其宽度。更宽的类型占更多位,更窄的类型占更少位。
当 32 位 CPU 需要修改一个 8 位值(如 char
)或 16 位值时,会发生什么?部分 32 位处理器(如 32 位 x86)可直接操作 8 位或 16 位数据,但速度往往低于 32 位操作;另一些 32 位 CPU(如 32 位 PowerPC)只能操作 32 位数据,必须借助额外技巧来处理更窄的值。
数值提升(numeric promotion)
为在多种架构上兼顾可移植性与性能,C++ 语言设计者不愿假设 CPU 必然能高效处理小于其“自然”数据宽度的值。为此,C++ 定义了一类称为数值提升的类型转换:将某些较窄的数值类型(如 char
)转换为较宽且 CPU 能有效处理的类型(通常为 int
或 double
)。
所有数值提升均为保值转换(value-preserving conversion,又称安全转换),即源类型的任何可能值都能在目标类型中得到相等值。由于提升是安全的,编译器会按需自动执行且不会给出警告。
数值提升减少冗余
数值提升还解决了另一难题。假设我们要写一个打印 int
的函数:
#include <iostream>
void printInt(int x)
{
std::cout << x << '\n';
}
若不存在类型转换,为了也能打印 short
或 char
,就得再为 short
写一个函数,再为 char
写一个……还得顾及 unsigned char
、signed char
、unsigned short
、wchar_t
、char8_t
、char16_t
、char32_t
,很快就会失控。
数值提升拯救了我们:只需编写参数为 int
和/或 double
的函数(如上例的 printInt
),即可用任何可被数值提升到这些类型的实参来调用。
数值提升的分类
数值提升规则分为两大类:整型提升(integral promotions)与浮点提升(floating point promotions)。仅列于这两类的转换才被视作数值提升。
浮点提升
较为简单:
根据浮点提升规则,类型 float
的值可转换为类型 double
。
因此,可以编写接受 double
的函数,并用 double
或 float
实参调用:
#include <iostream>
void printDouble(double d)
{
std::cout << d << '\n';
}
int main()
{
printDouble(5.0); // 无需转换
printDouble(4.0f); // float → double 数值提升
return 0;
}
第二次调用时,float
字面量 4.0f
被提升为 double
,使实参类型与形参匹配。
整型提升
规则更为复杂。按整型提升规则,可进行以下转换:
signed char
或signed short
→int
unsigned char
、char8_t
、unsigned short
→int
(若int
能容纳该类型全部取值范围),否则 →unsigned int
- 若
char
默认为signed
,则遵循signed char
规则;若默认为unsigned
,则遵循unsigned char
规则 bool
→int
,false
变 0,true
变 1
在 8 位字节且 int
至少 4 字节的常见环境下,上述规则意味着 bool
、char
、signed char
、unsigned char
、signed short
、unsigned short
均被提升为 int
。
另有少量更少见的整型提升规则,可参阅 cppreference 相关章节。
多数情况下,我们可编写参数为 int
的函数,并用多种整型实参调用:
#include <iostream>
void printInt(int x)
{
std::cout << x << '\n';
}
int main()
{
printInt(2);
short s{ 3 }; // 无 short 字面量后缀,用变量演示
printInt(s); // short → int 数值提升
printInt('a'); // char → int 数值提升
printInt(true); // bool → int 数值提升
return 0;
}
有两点值得注意:
- 在某些架构(如
int
仅 2 字节)上,部分无符号整型可能被提升为unsigned int
而非int
。 - 某些较窄的无符号类型(如
unsigned char
)可能被提升为较宽的有符号类型(如int
)。因此,整型提升虽保值,却不一定保留符号性(signed/unsigned)。
并非所有加宽转换都是数值提升
某些加宽转换(如 char
→ short
或 int
→ long
)在 C++ 中不被视为数值提升,而被归为数值转换(numeric conversions,将在 10.3 课讨论)。原因在于这些转换并不能帮助把较小类型转换为 CPU 更高效处理的大类型。
这一区别多数情况下仅属学术范畴。然而,在特定情形下,编译器会优先选择数值提升而非数值转换。我们将在 11.3 课 —— 函数重载决议与二义性匹配 中看到相关示例。