C++算术运算符详解
一元算术运算符
有两种一元算术运算符:正号 (+) 和负号 (-)。提醒一下,一元运算符是只接受一个操作数的运算符。
运算符 | 符号 | 形式 | 操作 |
---|---|---|---|
一元正号 | + | +x | x 的值 |
一元负号 | - | -x | x 的负值(取反) |
一元负号运算符返回操作数乘以 -1 的结果。换句话说,如果 x = 5
,那么 -x
就是 -5
。
一元正号运算符返回操作数的值。换句话说,+5
是 5
,+x
是 x
。通常你不需要使用这个运算符,因为它是冗余的。添加它主要是为了与一元负号运算符形成对称。
为了可读性,这两个运算符都应紧接在操作数之前(例如 -x
,而不是 - x
)。
不要将一元负号运算符与使用相同符号的二元减法运算符混淆。例如,在表达式 x = 5 - -3;
中,第一个负号是二元减法运算符,第二个是一元负号运算符。
二元算术运算符
有 5 种二元算术运算符。二元运算符是接受一个左操作数和一个右操作数的运算符。
运算符 | 符号 | 形式 | 操作 |
---|---|---|---|
加法 | + | x + y | x 加上 y |
减法 | - | x - y | x 减去 y |
乘法 | * | x * y | x 乘以 y |
除法 | / | x / y | x 除以 y |
取余 | % | x % y | x 除以 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 浮点格式的架构上,结果将是 NaN
或 Inf
。在其他架构上,结果很可能是未定义行为。
相关内容
我们在第 4.8 课 —— 浮点数中讨论了 NaN
和 Inf
。
你可以运行以下程序并输入 0
或 0.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) 的——它们只是使用操作数来计算并返回一个值。然而,两类内置运算符确实会修改它们的左操作数(同时也返回一个值):
- 赋值运算符:包括标准赋值运算符 (
=
)、算术赋值运算符 (+=
,-=
,*=
,/=
,%=
) 和位运算赋值运算符 (<<=
,>>=
,&=
,|=
,^=
)。 - 递增和递减运算符 (
++
和--
分别)。我们在第 6.4 课 —— 递增/递减运算符和副作用中讨论这些。
不在这个列表中的运算符有 ==
, !=
, <=
, 和 >=
,因为这些是非修改型的关系(比较)运算符(其中的 =
表示"等于")。我们在第 6.7 课 —— 关系运算符和浮点数比较中讨论这些。
供高级读者参考
重载的运算符可以被重新定义以具有与内置运算符不同的行为,这可能包括修改左操作数,即使内置版本不这样做(反之亦然)。例如,用于输出的重载 operator<<
会修改其左操作数(输出流对象)。