C++取余和指数运算详解
取余运算符 (operator%)
取余运算符 (通常也称为模运算符或模运算操作符) 是一种在进行整数除法后返回余数的运算符。例如,7 / 4 = 1
余数为 3
。因此,7 % 4 = 3
。另一个例子,25 / 7 = 3
余数为 4
,因此 25 % 7 = 4
。取余运算符仅适用于整数操作数。
这对于测试一个数是否能被另一个数整除(即除法后没有余数)非常有用:如果 x % y
的结果为 0
,那么我们就知道 x
能被 y
整除。
#include <iostream>
int main()
{
std::cout << "Enter an integer: ";
int x{};
std::cin >> x;
std::cout << "Enter another integer: ";
int y{};
std::cin >> y;
std::cout << "The remainder is: " << x % y << '\n';
if ((x % y) == 0)
std::cout << x << " is evenly divisible by " << y << '\n';
else
std::cout << x << " is not evenly divisible by " << y << '\n';
return 0;
}
以下是该程序的两次运行示例:
Enter an integer: 6
Enter another integer: 3
The remainder is: 0
6 is evenly divisible by 3
Enter an integer: 6
Enter another integer: 4
The remainder is: 2
6 is not evenly divisible by 4
现在让我们尝试一个第二个数比第一个数大的例子:
Enter an integer: 2
Enter another integer: 4
The remainder is: 2
2 is not evenly divisible by 4
余数为 2
可能一开始不太直观,但很简单:2 / 4
是 0
(使用整数除法),余数为 2
。每当第二个数大于第一个数时,第二个数能整除第一个数 0
次,因此第一个数就是余数。
带负数的取余运算
取余运算符也可以用于负数操作数。x % y
的结果总是带有 x
的符号。
运行上面的程序:
Enter an integer: -6
Enter another integer: 4
The remainder is: -2
-6 is not evenly divisible by 4
Enter an integer: 6
Enter another integer: -4
The remainder is: 2
6 is not evenly divisible by -4
在两种情况下,您都可以看到余数带有第一个操作数的符号。
术语命名 (Nomenclature)
C++ 标准实际上并没有给 operator%
指定一个名称。然而,C++20 标准确实说明:“二元 %
运算符产生第一个表达式除以第二个表达式的余数”。
尽管 operator%
常被称为"模"(modulo) 或"模数"(modulus) 运算符,但这可能会引起混淆,因为在数学中,模运算的定义方式通常会导致当操作数之一(且仅有一个)为负数时,其结果与 C++ 中的 operator%
产生的结果不同。
例如,在数学中: -21 模 4 = 3 -21 除以 4 的余数 = -1
出于这个原因,我们认为"取余"(remainder) 是比"模"(modulo) 更能准确描述 operator%
的名称。
在第一个操作数可能为负的情况下,必须注意余数也可能是负数。例如,您可能想这样写一个判断数字是否为奇数的函数:
bool isOdd(int x)
{
return (x % 2) == 1; // 当 x 为 -5 时会失败
}
然而,当 x
是负奇数时(例如 -5
),这将失败,因为 -5 % 2
是 -1
,而 -1 != 1
。
因此,如果您要比较取余运算的结果,最好与 0
比较,这没有正/负数的问题:
bool isOdd(int x)
{
return (x % 2) != 0; // 也可以写成 return (x % 2)
}
最佳实践 (Best practice)
如果可能,优先将取余运算符 (operator%
) 的结果与 0
进行比较。
指数运算符在哪里?
您会注意到,^
运算符(在数学中通常表示指数运算)在 C++ 中是按位异或 (Bitwise XOR) 操作(在课程 O.3 — 使用位运算符和位掩码进行位操作中介绍)。C++ 不包含指数运算符。
要在 C++ 中进行指数运算,需要 #include <cmath>
头文件,并使用 pow()
函数:
#include <cmath>
double x{ std::pow(3.0, 4.0) }; // 3 的 4 次方
注意,pow()
函数的参数(和返回值)类型是 double
。由于浮点数中的舍入误差,pow()
的结果可能不精确(即使您传递给它整数或整数)。
如果您想进行整数指数运算,最好使用自己的函数来实现。以下函数实现了整数指数运算(使用了不太直观但高效的"平方求幂法"算法):
#include <cassert> // for assert
#include <cstdint> // for std::int64_t
#include <iostream>
// 注意:指数 exp 必须为非负数
// 注意:不执行范围/溢出检查,请谨慎使用
constexpr std::int64_t powint(std::int64_t base, int exp)
{
assert(exp >= 0 && "powint: exp parameter has negative value"); // 断言确保 exp 非负
// 处理 base 为 0 的情况
if (base == 0)
return (exp == 0) ? 1 : 0; // 0^0 定义为 1,其他 0^exp 为 0
std::int64_t result{ 1 };
while (exp > 0)
{
if (exp & 1) // 如果 exp 是奇数
result *= base;
exp /= 2; // 等价于 exp = exp / 2;
base *= base; // 平方底数
}
return result;
}
int main()
{
std::cout << powint(7, 12) << '\n'; // 7 的 12 次方
return 0;
}
输出:
13841287201
如果您不理解这个函数的工作原理,不用担心 —— 调用它并不需要理解其原理。
相关内容 (Related content)
我们在第 9.6 课 —— 断言(assert)和静态断言(static_assert)中介绍断言(assert
),在第 F.1 课 —— 常量表达式函数(constexpr functions)中介绍常量表达式函数(constexpr
)。
供高级读者参考 (For advanced readers)
constexpr
说明符允许函数在用于常量表达式时在编译时求值;否则,它的行为就像常规函数并在运行时求值。
警告 (Warning)
在绝大多数情况下,整数指数运算会溢出整数类型。这很可能是标准库一开始没有包含这样一个函数的原因。
以下是上面指数函数的一个更安全版本,它检查了溢出:
#include <cassert> // for assert
#include <cstdint> // for std::int64_t
#include <iostream>
#include <limits> // for std::numeric_limits
// powint() 的一个更安全(但更慢)版本,检查溢出
// 注意:指数 exp 必须为非负数
// 如果发生溢出,返回 std::numeric_limits<std::int64_t>::max()
constexpr std::int64_t powint_safe(std::int64_t base, int exp)
{
assert(exp >= 0 && "powint_safe: exp parameter has negative value"); // 断言确保 exp 非负
// 处理 base 为 0 的情况
if (base == 0)
return (exp == 0) ? 1 : 0;
std::int64_t result { 1 };
// 为了使范围检查更容易,我们将确保 base 是正数
// 如果需要,我们会在最后翻转结果的符号
bool negativeResult{ false };
if (base < 0)
{
base = -base; // 将 base 变为正数
negativeResult = (exp & 1); // 如果指数是奇数,结果应为负数
}
while (exp > 0)
{
if (exp & 1) // 如果 exp 是奇数
{
// 检查 result 乘以 base 是否会溢出
if (result > std::numeric_limits<std::int64_t>::max() / base)
{
std::cerr << "powint_safe(): result overflowed\n"; // 报告结果溢出
return std::numeric_limits<std::int64_t>::max(); // 返回最大可表示值
}
result *= base; // 将当前 base 乘入结果
}
exp /= 2; // 指数减半
// 如果已完成,在此处跳出循环
if (exp <= 0)
break;
// 以下代码仅在需要再次迭代时才需要执行
// 检查 base 乘以自身是否会溢出(用于下一次迭代的平方)
if (base > std::numeric_limits<std::int64_t>::max() / base)
{
std::cerr << "powint_safe(): base overflowed\n"; // 报告底数溢出
return std::numeric_limits<std::int64_t>::max(); // 返回最大可表示值
}
base *= base; // 平方底数
}
if (negativeResult)
return -result; // 如果结果应为负,返回负值
return result;
}
int main()
{
std::cout << powint_safe(7, 12) << '\n'; // 7 的 12 次方 (未溢出)
std::cout << powint_safe(70, 12) << '\n'; // 70 的 12 次方 (将返回最大的 64 位 int 值)
return 0;
}
测验时间 (Quiz time)
问题 #1
以下表达式的结果是什么? 6 + 5 * 4 % 3
显示答案
问题 #2
编写一个程序,要求用户输入一个整数,并告诉用户该数字是偶数还是奇数。编写一个名为 isEven()
的 constexpr
函数,如果传递给它的整数是偶数则返回 true
,否则返回 false
。使用取余运算符测试整数参数是否为偶数。确保 isEven()
对正数和负数都有效。
显示提示
您的程序应匹配以下输出:
Enter an integer: 5
5 is odd