10.5 — 算术转换

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

考虑以下表达式

int x { 2 + 3 };

二元 `operator+` 接收两个操作数,都为 `int` 类型。由于两个操作数类型相同,因此将使用该类型执行计算,并且返回的值也将是该类型。因此,`2 + 3` 将求值为 `int` 值 `5`。

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

??? y { 2 + 3.5 };

在这种情况下,`operator+` 接收一个 `int` 类型的操作数和一个 `double` 类型的操作数。运算符的结果应该返回为 `int`、`double`,还是完全是其他类型?

在 C++ 中,某些运算符要求其操作数类型相同。如果其中一个运算符与不同类型的操作数一起调用,则一个或两个操作数将使用一组称为**常用算术转换**的规则隐式转换为匹配类型。作为常用算术转换规则的结果而生成的匹配类型称为操作数的**公共类型**。

需要相同类型操作数的运算符

以下运算符要求其操作数类型相同

  • 二元算术运算符:+、-、*、/、%
  • 二元关系运算符:<、>、<=、>=、==、!=
  • 二元位算术运算符:&、^、|
  • 条件运算符 ?:(不包括条件,条件预期为 `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`。

在作者的机器上,这会打印出

int
double
double 5.5

请注意,您的编译器可能会显示略有不同的内容,因为 `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`,正如您所期望的

int 9

有符号和无符号问题

这种优先级层次结构和转换规则在混合有符号和无符号值时可能会导致一些问题。例如,请看以下代码

#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`。但实际结果是

unsigned int 4294967291

由于转换规则,`int` 操作数转换为 `unsigned int`。并且由于值 `-5` 超出了 `unsigned int` 的范围,我们得到了一个不期望的结果。

这里是另一个显示反直觉结果的例子

#include <iostream>

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

    return 0;
}

虽然我们清楚 5 大于 -3,但当此表达式求值时,-3 会转换为一个大于 5 的大 unsigned int。因此,以上打印 false 而不是预期的 true

这是避免使用无符号整数的主要原因之一——当您在算术表达式中将它们与有符号整数混合时,您可能会得到意想不到的结果。而且编译器甚至可能不会发出警告。

std::common_typestd::common_type_t

在未来的课程中,我们将遇到了解两种类型的公共类型很有用的情况。`std::common_type` 和有用的类型别名 `std::common_type_t`(两者都定义在 头文件中)正可以用于此目的。

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

我们将在课程 11.8 -- 具有多个模板类型的函数模板 中展示一个这很有用的示例。

guest
您的电子邮箱地址将不会被显示
发现错误?请在上方留言!
与勘误相关的评论在处理后将被删除,以帮助减少混乱。感谢您帮助使网站对每个人都更好!
来自 https://gravatar.com/ 的头像与您提供的电子邮箱地址相关联。
有回复时通知我:  
64 条评论
最新
最早 最多投票
内联反馈
查看所有评论