考虑以下表达式:
int x{ 2 + 3 };
二元运算符 +
接收两个操作数,二者均为 int
类型。由于类型相同,计算将以该类型进行,返回结果亦为同一类型,因此 2 + 3
求值为 int
类型的 5
。
但若二元运算符的两个操作数类型不同呢?
auto y{ 2 + 3.5 };
此时,运算符 +
的一个操作数为 int
,另一个为 double
。结果应当返回 int
、double
,还是其他类型?
在 C++ 中,某些运算符要求其操作数类型必须相同。如果调用这些运算符时操作数类型不同,编译器会根据一套称为通常算术转换(usual arithmetic conversions)的规则,将一个或两个操作数隐式转换为匹配类型。转换后得到的共同类型称为公共类型(common type)。
要求操作数类型必须相同的运算符
以下运算符要求操作数类型相同:
- 二元算术运算符:
+
、-
、*
、/
、%
- 二元关系运算符:
<
、>
、<=
、>=
、==
、!=
- 二元位运算运算符:
&
、^
、|
- 条件运算符
?:
(条件表达式本身需为bool
,其余两操作数需同类型)
供进阶读者参考
重载运算符不受通常算术转换规则约束。
通常算术转换规则
通常算术转换规则较为复杂,此处略作简化。编译器内部维护一个类型优先级列表,大致如下(从高到低):
long double (最高)
double
float
long long
long
int (最低)
寻找公共类型的步骤如下:
步骤 1
- 若一个操作数为整型,另一个为浮点型,则整型操作数转换为浮点操作数的类型(不进行整型提升)。
- 否则,所有整型操作数先进行数值提升(参见 10.2 —— 浮点与整型提升)。
步骤 2
- 提升后,若一个操作数为有符号,另一个为无符号,则应用特殊规则(见下文)。
- 否则,优先级较低的操作数转换为优先级较高者的类型。
供进阶读者参考
整型不同符号的特殊匹配规则:
- 若无符号操作数的优先级 不低于 有符号操作数,则有符号操作数转换为无符号操作数的类型。
- 若有符号操作数的类型可表示无符号操作数类型的全部值,则无符号操作数转换为有符号操作数的类型。
- 否则,两操作数均转换为有符号类型对应的无符号版本。
相关内容
完整规则可参阅 cppreference 页面。
示例
示例 1:int 与 double 相加
#include <iostream>
#include <typeinfo>
int main()
{
int i{ 2 };
double d{ 3.5 };
std::cout << typeid(i + d).name() << ' ' << i + d << '\n';
return 0;
}
double
优先级最高,因此 int
类型的 i
被转换为 double
值 2.0
,然后 2.0 + 3.5
结果为 double
类型的 5.5
。
(typeid().name()
的输出由实现决定,可能略有差异。)
示例 2:两个 short 相加
#include <iostream>
#include <typeinfo>
int main()
{
short a{ 4 }, b{ 5 };
std::cout << typeid(a + b).name() << ' ' << a + b << '\n';
return 0;
}
两操作数均未出现在优先级列表中,因此二者均经整型提升为 int
,结果为 int
类型的 9
。
有符号与无符号混用的问题
上述优先级与转换规则在混用有符号与无符号值时可能导致意外结果。例如:
#include <iostream>
#include <typeinfo>
int main()
{
std::cout << typeid(5u - 10).name() << ' ' << 5u - 10 << '\n';
return 0;
}
期望 5 - 10 = -5
,实际输出:
unsigned int 4294967291
由于转换规则,int
操作数被转换为 unsigned int
,-5
不在其范围内,结果出现回绕。
再看比较运算:
#include <iostream>
int main()
{
std::cout << std::boolalpha << (-3 < 5u) << '\n';
return 0;
}
直观上 -3 < 5
应为 true
,但 -3
被转换为巨大的无符号整数,结果输出 false
。
这也是建议避免混用无符号与有符号整数的主要原因:编译器通常不会给出警告,却可能产生意外行为。
std::common_type 与 std::common_type_t
后续课程将遇到需要获知两个类型的公共类型的场景。<type_traits>
中提供的 std::common_type
及其类型别名 std::common_type_t
可用于此目的。
示例:
std::common_type_t<int, double>
返回double
;std::common_type_t<unsigned int, long>
返回long
(或更宽的无符号类型,取决于平台)。
我们将在 11.8 课 —— 多模板类型函数模板 中展示其应用实例。