C++ 算术运算符完全指南:一元和二元运算符详解

C++算术运算符详解

一元算术运算符

有两种一元算术运算符:正号 (+) 和负号 (-)。提醒一下,一元运算符是只接受一个操作数的运算符。

运算符符号形式操作
一元正号++xx 的值
一元负号--xx 的负值(取反)

一元负号运算符返回操作数乘以 -1 的结果。换句话说,如果 x = 5,那么 -x 就是 -5

一元正号运算符返回操作数的值。换句话说,+55+xx。通常你不需要使用这个运算符,因为它是冗余的。添加它主要是为了与一元负号运算符形成对称。

为了可读性,这两个运算符都应紧接在操作数之前(例如 -x,而不是 - x)。

不要将一元负号运算符与使用相同符号的二元减法运算符混淆。例如,在表达式 x = 5 - -3; 中,第一个负号是二元减法运算符,第二个是一元负号运算符。

二元算术运算符

有 5 种二元算术运算符。二元运算符是接受一个左操作数和一个右操作数的运算符。

运算符符号形式操作
加法+x + yx 加上 y
减法-x - yx 减去 y
乘法*x * yx 乘以 y
除法/x / yx 除以 y
取余%x % yx 除以 y 的余数

加法、减法和乘法运算符的工作方式与现实生活中的完全一样,没有特殊情况。

除法和取余需要一些额外的解释。我们将在下面讨论除法,在下一课讨论取余。

整数除法和浮点数除法

最容易理解除法运算符有两种不同的"模式"。

如果其中一个(或两个)操作数是浮点数值,则除法运算符执行浮点数除法。浮点数除法返回一个浮点数值,并保留小数部分。例如,7.0 / 4 = 1.75, 7 / 4.0 = 1.75, 以及 7.0 / 4.0 = 1.75。与所有浮点算术运算一样,可能会发生舍入误差。

如果两个操作数都是整数,则除法运算符执行整数除法。整数除法会丢弃任何小数部分并返回一个整数值。例如,7 / 4 = 1,因为结果的小数部分被丢弃了。类似地,-7 / 4 = -1,因为小数部分被丢弃。

使用 static_cast<> 对整数进行浮点数除法

以上引出了一个问题——如果我们有两个整数,并且想在不丢失小数部分的情况下对它们进行除法运算,我们该怎么做?

在第 4.12 课 —— 类型转换和 static_cast 简介中,我们展示了如何使用 static_cast<> 运算符将 char 转换为整数,以便将其作为整数而非字符打印。

我们可以类似地使用 static_cast<> 将整数转换为浮点数,以便执行浮点数除法而不是整数除法。考虑以下代码:

#include <iostream>

int main()
{
    constexpr int x{ 7 };
    constexpr int y{ 4 };

    std::cout << "int / int = " << x / y << '\n';
    std::cout << "double / int = " << static_cast<double>(x) / y << '\n';
    std::cout << "int / double = " << x / static_cast<double>(y) << '\n';
    std::cout << "double / double = " << static_cast<double>(x) / static_cast<double>(y) << '\n';

    return 0;
}

这将产生以下结果:

int / int = 1
double / int = 1.75
int / double = 1.75
double / double = 1.75

以上说明,如果任一操作数是浮点数,结果将是浮点数除法,而不是整数除法。

除以 0 和 0.0

除数为 0 的整数除法将导致未定义行为 (undefined behavior),因为数学上其结果未定义!

#include <iostream>

int main()
{
    constexpr int apples { 12 };
    std::cout << "You have " << apples << " apples. Enter how many people to divide them between: ";
    int x {};
    std::cin >> x;

    std::cout << "Each person gets " << apples / x << " whole apples.\n"; // apples 和 x 是 int,因此这是整数除法

    return 0;
}

如果你运行上面的程序并输入 0,你的程序很可能会崩溃。尽管试试,它不会损害你的计算机。

除以浮点数值 0.0 的结果是实现定义的 (implementation-defined)(意味着行为由编译器/架构决定)。在支持 IEEE754 浮点格式的架构上,结果将是 NaNInf。在其他架构上,结果很可能是未定义行为。

相关内容

我们在第 4.8 课 —— 浮点数中讨论了 NaNInf

你可以运行以下程序并输入 00.0 来查看你的程序会发生什么:

#include <iostream>

int main()
{
    constexpr int apples { 12 };
    std::cout << "You have " << apples << " apples. Enter how many servings of apples you want: ";

    double d {};
    std::cin >> d;

    std::cout << "Each serving is " << apples / d << " apples.\n"; // d 是 double,因此这是浮点数除法

    return 0;
}

算术赋值运算符

运算符符号形式操作
加法赋值+=x += y将 y 加到 x 上
减法赋值-=x -= y从 x 中减去 y
乘法赋值*=x *= y将 x 乘以 y
除法赋值/=x /= y将 x 除以 y
取余赋值%=x %= y将 x / y 的余数放入 x

到目前为止,当你需要给一个变量加 4 时,你很可能这样做:

x = x + 4; // 给 x 的现有值加 4

这可行,但有点笨拙,并且需要两个运算符来执行(operator+operator=)。

因为像 x = x + 4 这样的语句非常常见,C++ 为了方便提供了五种算术赋值运算符。你可以写 x += 4 来代替 x = x + 4。你可以写 x *= y 来代替 x = x * y

因此,上面的代码变为:

x += 4; // 给 x 的现有值加 4

修改型和非修改型运算符

可以修改其操作数值的运算符,非正式地称为修改型运算符 (modifying operator)。在 C++ 中,大多数运算符是非修改型 (non-modifying) 的——它们只是使用操作数来计算并返回一个值。然而,两类内置运算符确实会修改它们的左操作数(同时也返回一个值):

  1. 赋值运算符:包括标准赋值运算符 (=)、算术赋值运算符 (+=, -=, *=, /=, %=) 和位运算赋值运算符 (<<=, >>=, &=, |=, ^=)。
  2. 递增和递减运算符 (++-- 分别)。我们在第 6.4 课 —— 递增/递减运算符和副作用中讨论这些。

不在这个列表中的运算符有 ==, !=, <=, 和 >=,因为这些是非修改型的关系(比较)运算符(其中的 = 表示"等于")。我们在第 6.7 课 —— 关系运算符和浮点数比较中讨论这些。

供高级读者参考

重载的运算符可以被重新定义以具有与内置运算符不同的行为,这可能包括修改左操作数,即使内置版本不这样做(反之亦然)。例如,用于输出的重载 operator<< 会修改其左操作数(输出流对象)。


关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"