跳至内容
Topos Uranos

摘要

算术表达式并不像阅读时那样从左到右计算,而是按照一个隐藏的结构即它的树来计算,这棵树由运算符的优先级、结合性和括号决定;在这棵树的每个节点上,操作数的类型决定进行哪种运算,因此 7 / 2 的值是 3,而 7 / 2.0 的值是 3.5。本课研究 C++ 的五个算术运算符在整数和实数上的用法;整数除法和余数,自 C++11 起它们向零截断,由此可知余数与被除数同号,并且在被除数为负时与欧几里得除法的余数不同;求欧几里得余数的方法;表达式树;操作数类型不同时结果的类型;以及整数算术表达式失去意义的两种方式:除以零和在中间计算中溢出。

学习目标

  1. 计算 C++ 中整数除法的商和余数(包括操作数为负的情形),并把它们与欧几里得除法的商和余数联系起来。
  2. 按照优先级和结合性画出算术表达式的树,并计算它的值。
  3. 预测不同类型操作数之间运算结果的类型。
  4. 用整数除法和余数分解数量:一个数的各位数字、以小时和分钟表示的时长、星期几。

算术运算符

C++ 有五个二元算术运算符:加法 +、减法 -、乘法 *、除法 / 和取余 %;此外还有一元负号,它改变其唯一操作数的符号,如 -x,以及一元正号 +x,它让操作数保持原样。前四个适用于整数和实数;取余只适用于整数。然而,同样的写法并非在所有情况下都表示同一种运算:如果 / 的两个操作数都是整数,这种除法就是整数除法,其结果是整数,即去掉小数部分的商;如果其中有一个是实数,除法就是实数除法。字符、布尔值、类型转换与常量一课展示了这条规则最明显的后果:double r{7 / 2}; 存储的是 3,因为在结果转换为实数之前,除法已经在整数之间完成了。

例题两个整数上的五个运算符

程序把五个运算符作用于 17 和 5,并用一个实数被除数重复除法。

C++ 程序
#include <print>​int main(){    const int a{17};    const int b{5};    std::println("{} + {} = {}", a, b, a + b);    std::println("{} - {} = {}", a, b, a - b);    std::println("{} * {} = {}", a, b, a * b);    std::println("{} / {} = {}", a, b, a / b);    std::println("{} % {} = {}", a, b, a % b);    const double x{17.0};    std::println("{} / {} = {}", x, b, x / b);}
输出
17 + 5 = 2217 - 5 = 1217 * 5 = 8517 / 5 = 317 % 5 = 217 / 5 = 3.4

17 除以 5 得 3,余 2:这就是把 17 个物体按每组 5 个分组时的商和余数。最后一行用一个实数除以一个整数,得到 3.4。需要注意,std::println 把实数 17.0 写成“17”:空占位符写出值的最短表示,因此写出的内容不会透露类型;要看出类型,可以用 {:.1f} 要求写出小数。

运算符 % 不接受实数。写 7.5 % 2 不是程序运行时才发现的逻辑错误,而是编译错误:编译器指出,类型为 double 和 int 的操作数对该运算符无效。

C++ 程序
#include <print>​int main(){    std::println("{}", 7.5 % 2);}
编译器错误信息
main.cpp: In function ‘int main()’:main.cpp:5:28: error: invalid operands of types ‘double’ and ‘int’ to binary ‘operator%’    5 |     std::println("{}", 7.5 % 2);      |                        ~~~ ^ ~

实数除法的余数是存在的,它由头文件 <cmath> 中的函数 std::fmod 计算,这将在位与数学库一课中与乘幂一起学习:C++ 没有乘幂运算符,2 ^ 3 的值不是 8,而是另外的东西,那里会加以解释。