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;
}

以下是该程序的几次运行结果:

image

image

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

image

余数为2的情况初看可能不太明显,但原理很简单:2除以4等于0(采用整数除法),余数为2。当被除数大于除数时,除数只能除出0次,因此被除数即为余数。


带负数的余数运算

余数运算符也可处理负数操作数。x % y 始终返回与 x 同号的结果。

运行上述程序:

image

image

在两种情况下,你都可以看到余数取第一个操作数的符号。

命名法

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++中进行幂运算,需包含头文件,并使用pow()函数:

#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;
}

生产:

image

不必担心你是否理解这个函数的工作原理——调用它并不需要理解其工作原理。

相关内容
我们在第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;
}

image


测验时间

问题 #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

image
image
image

显示解答

#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;
}
posted @ 2026-02-19 09:05  游翔  阅读(1)  评论(0)    收藏  举报