10-5 算术转换

第6.1课——运算符优先级与结合性中,我们讨论了表达式如何根据运算符的优先级和结合性进行求值。

考虑以下表达式:

int x { 2 + 3 };

二元运算符operator+接收两个操作数,均为int类型。由于两者类型相同,计算将使用该类型进行,返回值也将保持此类型。因此2 + 3将求解为int值5。

但当二元运算符的操作数类型不同时会发生什么?

??? y { 2 + 3.5 };

此时加法运算符operator+接收一个 int 类型操作数和一个 double 类型操作数。运算结果应返回为 int、double,还是可能完全不同的类型?

在C++中,某些运算符要求其操作数类型一致。若此类运算符被不同类型的操作数调用,则会根据称为常规算术转换usual arithmetic conversions的规则集,将一个或两个操作数隐式转换为匹配类型。通过常规算术转换规则生成的匹配类型称为操作数的公共类型common type


要求操作数类型一致的运算符

下列运算符要求其操作数类型一致:

  • 二元算术运算符:+、-、*、/、%
  • 二元关系运算符:<、>、<=、>=、==、!=
  • 二元位运算符:&、^、|
  • 条件运算符?:(条件部分预期为bool类型除外)

对于进阶读者
重载运算符不受常规算术转换规则约束。


常规算术转换规则

常规转换规则较为复杂,此处简化说明:编译器采用分层类型序列,大致如下:

  • long double(最高层级)
  • double
  • float
  • long long
  • long
  • int(最低层级)

匹配类型时遵循以下规则:

步骤 1:

若一个操作数为整数的类型,另一个为浮点类型,则整数操作数转换为浮点操作数的类型(不进行整数提升)。
否则,所有整数操作数均进行数值提升(参见10.2节——浮点与整数提升规则)。

步骤2:

  • 提升后,若一个操作数带符号而另一个无符号,则适用特殊规则(见下文)
  • 否则,低等级操作数转换为高等级操作数的类型。

对于进阶读者

带符号整数操作数的特殊匹配规则:

  • 若无符号操作数的位数大于或等于有符号操作数,则有符号操作数转换为无符号操作数类型。
  • 若有符号操作数类型能表示无符号操作数类型的全部值域,则无符号操作数转换为有符号操作数类型。
  • 否则,两个操作数都会转换为有符号操作数对应的无符号类型。

相关内容
您可以在这里找到常规算术转换的完整规则。


一些示例

在以下示例中,我们将使用 typeid 运算符(包含在 头文件中)来显示表达式的结果类型。

首先,让我们将一个 int 和一个 double 相加:

#include <iostream>
#include <typeinfo> // for typeid()

int main()
{
    int i{ 2 };
    std::cout << typeid(i).name() << '\n'; // show us the name of the type for i

    double d{ 3.5 };
    std::cout << typeid(d).name() << '\n'; // show us the name of the type for d

    std::cout << typeid(i + d).name() << ' ' << i + d << '\n'; // show us the type of i + d

    return 0;
}

在此情况下,double操作数具有最高优先级,因此优先级较低的int类型操作数被转换为double值2.0。随后将double值2.0与3.5相加,产生double结果5.5。

在作者机器上输出如下:

image

请注意您的编译器可能显示略有差异的结果,因为typeid.name()输出的名称取决于具体实现。

现在尝试加法两个short类型值:

#include <iostream>
#include <typeinfo> // for typeid()

int main()
{
    short a{ 4 };
    short b{ 5 };
    std::cout << typeid(a + b).name() << ' ' << a + b << '\n'; // show us the type of a + b

    return 0;
}

由于两个操作数均不在优先级列表中,它们都将进行整数提升转换为int类型。两个int相加的结果自然是int类型:

image


有符号与无符号问题

这种优先级层次结构和转换规则在混合使用有符号与无符号值时可能引发问题。例如以下代码:

#include <iostream>
#include <typeinfo> // for typeid()

int main()
{
    std::cout << typeid(5u-10).name() << ' ' << 5u - 10 << '\n'; // 5u means treat 5 as an unsigned integer

    return 0;
}

你可能预期表达式 5u - 10 计算结果为 -5(因 5 - 10 = -5)。但实际结果如下:

image

补充内容:截至2026年3月,编码规则见Itanium C++ ABI
image

由于转换规则,int 操作数被转换为无符号整数。由于 -5 超出无符号整数范围,最终得到出乎意料的结果。

另一个反直觉结果示例如下:

#include <iostream>

int main()
{
    std::cout << std::boolalpha << (-3 < 5u) << '\n';

    return 0;
}

虽然我们清楚5大于-3,但当表达式求值时,-3会被转换为大于5的大型无符号整数。因此上述代码输出false而非预期的true。

image

这正是应避免使用无符号整数的首要原因——当它们与有符号整数混用在算术表达式中时,极易产生意外结果。而编译器通常不会发出警告。


std::common_type 与 std::common_type_t

后续课程中,我们将遇到需要确定两个类型公共类型的场景。std::common_type 及其实用别名 std::common_type_t(均定义于 <type_traits> 头文件)正是为此目的而生。

例如,std::common_type_t<int, double> 返回 int 和 double 的公共类型,而 std::common_type_t<unsigned int, long>返回 unsigned int 和 long 的公共类型。

我们将在第11.8节——带有多个模板类型的函数模板中展示一个应用示例。

posted @ 2026-03-03 17:43  游翔  阅读(1)  评论(0)    收藏  举报