6-3 余数与幂运算
取余运算符(operator%运算符)
取余运算符remainder operator(也常称为模运算符modulo operator或模数运算符modulus 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;
}
以下是该程序的几次运行结果:


现在我们来尝试一个例子,其中第二个数字大于第一个数字:

余数为2的情况初看可能不太明显,但原理很简单:2除以4等于0(采用整数除法),余数为2。当被除数大于除数时,除数只能除出0次,因此被除数即为余数。
带负数的余数运算
余数运算符也可处理负数操作数。x % y 始终返回与 x 同号的结果。
运行上述程序:


在两种情况下,你都可以看到余数取第一个操作数的符号。
命名法
C++标准并未为operator%运算符赋予正式名称。但C++20标准明确指出:“二元%运算符返回第一个表达式除以第二个表达式的余数”。
尽管%运算符常被称为“模运算符
modulo”或“模数运算符modulus”,但这种称谓可能造成混淆——因为数学中的模运算在定义上,当一个(且仅一个)操作数为负值时,其结果与C++中operator%运算符的输出存在差异。例如在数学中:
-21 mod 4 = 3
-21 除以 4 的余数 = -1因此我们认为,相较于“模运算符
modulo”,“余数运算符remainder”是%运算符更准确的命名。
(方便理解,补充一下这个例子的计算)
定义 验证 数学模 -21 = 4 × (-6) + 3✓截断余 -21 = 4 × (-5) + (-1)✓
当第一个操作数可能为负值时,必须注意余数也可能为负值。例如,你可能会考虑编写一个函数来判断一个数是否为奇数,如下所示:
bool isOdd(int x)
{
return (x % 2) == 1; // fails when x is -5
}
然而,当x为负奇数(如-5)时,此方法将失效,因为-5 % 2 等于-1,而-1不等于1。
因此,若需比较余数运算结果,建议将其与0进行比较——0不存在正负数问题:
bool isOdd(int x)
{
return (x % 2) != 0; // could also write return (x % 2)
}
最佳实践
尽可能将取余运算符(operator%运算符)的结果与0进行比较。
幂运算符(exponent operator)在哪里?
你会注意到,^运算符(数学中常用以表示幂运算)在C++中是位异或Bitwise XOR运算(详见O.3课——位运算符与位掩码的位操作)。C++中并不包含幂运算符。
要在C++中进行幂运算,需包含
#include <cmath>
double x{ std::pow(3.0, 4.0) }; // 3 to the 4th power
请注意,pow()函数的形参(及返回值)类型为double。由于浮点数中的舍入误差,pow()的结果可能不够精确(即使传入的是整数或整数)。
若需进行整数幂运算,建议使用自定义函数实现。下述函数通过效率较高的“平方运算实现幂运算”(非直观算法)实现了整数幂运算:
#include <cassert> // for assert
#include <cstdint> // for std::int64_t
#include <iostream>
// note: exp must be non-negative
// note: does not perform range/overflow checking, use with caution
constexpr std::int64_t powint(std::int64_t base, int exp)
{
assert(exp >= 0 && "powint: exp parameter has negative value");
// Handle 0 case
if (base == 0)
return (exp == 0) ? 1 : 0;
std::int64_t result{ 1 };
while (exp > 0)
{
if (exp & 1) // if exp is odd
result *= base;
exp /= 2;
base *= base;
}
return result;
}
int main()
{
std::cout << powint(7, 12) << '\n'; // 7 to the 12th power
return 0;
}
生产:

不必担心你是否理解这个函数的工作原理——调用它并不需要理解其工作原理。
相关内容
我们在第9.6节——断言与static_assert中介绍了断言,并在F.1节——常量表达式函数中讲解了常量表达式函数。
对于进阶读者
常量表达式修饰符使函数在常量表达式中使用时能在编译时求值;否则它将像普通函数一样在运行时求值。
警告
绝大多数情况下,整数幂运算会导致整数类型溢出。这很可能是标准库最初未提供此类函数的原因。
以下是上述幂运算函数的安全版本,会检查溢出情况:
#include <cassert> // for assert
#include <cstdint> // for std::int64_t
#include <iostream>
#include <limits> // for std::numeric_limits
// A safer (but slower) version of powint() that checks for overflow
// note: exp must be non-negative
// Returns std::numeric_limits<std::int64_t>::max() if overflow occurs
constexpr std::int64_t powint_safe(std::int64_t base, int exp)
{
assert(exp >= 0 && "powint_safe: exp parameter has negative value");
// Handle 0 case
if (base == 0)
return (exp == 0) ? 1 : 0;
std::int64_t result { 1 };
// To make the range checks easier, we'll ensure base is positive
// We'll flip the result at the end if needed
bool negativeResult{ false };
if (base < 0)
{
base = -base;
negativeResult = (exp & 1);
}
while (exp > 0)
{
if (exp & 1) // if exp is odd
{
// Check if result will overflow when multiplied by 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;
}
exp /= 2;
// If we're done, get out here
if (exp <= 0)
break;
// The following only needs to execute if we're going to iterate again
// Check if base will overflow when multiplied by 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 to the 12th power
std::cout << powint_safe(70, 12) << '\n'; // 70 to the 12th power (will return the max 64-bit int value)
return 0;
}

测验时间
问题 #1
下列表达式的计算结果是什么? 6 + 5 * 4 % 3
显示解答
由于 * 和 % 的运算优先级高于 +,因此 + 将最后求值。我们可以将表达式重写为 6 + (5 * 4 % 3)。运算符 * 和 % 的优先级相同,因此需要通过结合律来求解。* 和 % 的结合律为从左到右,故先求解左侧运算符。表达式可重写为:6 + ((5 * 4) % 3)。
6 + ((5 * 4) % 3) = 6 + (20 % 3) = 6 + 2 = 8
问题 #2
编写一个程序,要求用户输入一个整数,并告知该数是偶数还是奇数。编写名为 isEven() 的 constexpr 函数,若传入的整数为偶数则返回 true,否则返回 false。使用取余运算符判断整数参数是否为偶数。确保 isEven() 同时适用于正数和负数。
显示提示
提示:编写此程序时可能需要使用if语句和比较运算符(==)。若需复习相关操作方法,请参阅第4.9课——布尔值。
程序输出应与以下示例一致:
Enter an integer: 5
5 is odd



显示解答
#include <iostream>
constexpr bool isEven(int x)
{
// if x % 2 == 0, 2 divides evenly into our number, which means it must be an even number
return (x % 2) == 0;
}
int main()
{
std::cout << "Enter an integer: ";
int x{};
std::cin >> x;
if (isEven(x))
std::cout << x << " is even\n";
else
std::cout << x << " is odd\n";
return 0;
}
注意:你可能曾想过这样编写 isEven() 函数:(哈哈,我是这种)
constexpr bool isEven(int x)
{
if ((x % 2) == 0)
return true;
else
return false;
}
虽然这段代码能正常运行,但实现方式过于复杂。让我们看看如何简化它。首先,将if语句的条件部分提取出来,并赋值给一个独立的布尔变量:
constexpr bool isEven(int x)
{
bool isEven = (x % 2) == 0;
if (isEven) // isEven is true
return true;
else // isEven is false
return false;
}
请注意,上述if语句本质上表示“如果isEven为真,则返回真;否则,如果isEven为假,则返回假”。既然如此,我们直接返回isEven即可:
constexpr bool isEven(int x)
{
bool isEven = (x % 2) == 0;
return isEven;
}
在这种情况下,由于变量 isEven 仅被使用一次,我们不妨直接将其移除:
constexpr bool isEven(int x)
{
return (x % 2) == 0;
}

浙公网安备 33010602011771号