CC++ & Algorithm

模运算与取余——数轴上绕圈子的学问

中等3
语言版本:C++
概述:理解模运算(取余)的含义和基本性质,并用C++中的 % 运算符解决问题。

模运算与取余——数轴上绕圈子的学问

你有没有想过,时钟为什么是12小时制?如果现在是11点,再过3小时,不是11+3=14点,而是14 mod 12 = 2点,也就是下午2点。这个“mod”就是取余运算,在C++中用百分号 % 表示。简单说,取余就是求一个数除以另一个数后剩下的“零头”。

模运算(取余)在编程里超级有用:判断奇偶、控制循环、处理大数、解决日常问题(比如分零食、算星期几)。理解它,就像在数轴上“绕圈子”——每次加一圈,又回到起点。


1. 什么是取余?生活中的“绕圈子”

取余 a % b 计算的是 a 除以 b 的余数。规则是:余数必须小于除数,且大于等于0(正数情况下)。

生活中的例子:

  • 分糖果:你有14颗糖,要平均分给3个小朋友,每人分4颗,剩下2颗。14 % 3 = 2。
  • 排队:你们班40人,每7人排一队,最后一队有多少人?40 % 7 = 5,最后5人单独一队。
  • 星期几:今天是星期一,过了10天是星期几?(1 + 10) % 7 = 4,星期四(星期一算1,星期二2,……星期日0或7,这里约定星期一到日用17,但更常见的是06)。

这些例子都说明取余就是“绕圈子”,结果总是落在0到(除数-1)之间。


2. C++中的 % 运算符

在C++中,% 只能用于整数(int、long、long long等)。用法和加减乘除一样,但要注意:除数不能为0,否则程序崩溃。

语法:

int result = a % b;  // 变量a除以变量b的余数

常见场景:取个位数

int num = 12345;
int ge = num % 10;   // ge = 5,相当于切掉十位以上,只留个位

关于负数: 初学阶段我们只处理正数。如果你遇到负数,C++的 % 结果可能为负(比如 -5 % 3 在C++中可能是 -2,数学上更习惯取正余数1)。不过别担心,CSP-J题目通常明确说“输入非负整数”,我们只关心正数情况。


3. 模运算的常见用途

✅ 判断奇数偶数

任何整数 n,如果 n % 2 == 0 就是偶数,否则是奇数。

int n = 7;
if (n % 2 == 0)
    cout << "偶数" << endl;
else
    cout << "奇数" << endl;

✅ 判断闰年

闰年规则:能被4整除但不能被100整除,或者能被400整除。

int year = 2024;
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
    cout << year << "是闰年" << endl;

✅ 循环节控制(比如星期几、数组下标轮转)

假设有一个长度为3的数组,我们想循环访问元素:下标0→1→2→0→1→2……可以用 index = (index + 1) % 3 实现。

int index = 0;
index = (index + 1) % 3;  // index变成1
index = (index + 1) % 3;  // index变成2
index = (index + 1) % 3;  // index变成0,回到起点

✅ 大数防溢出(取模运算)

当数字很大时,比如计算阶乘或斐波那契数列,结果会超过int范围。我们经常只关心结果对某个数取模,比如模 10007

int fib[100];
fib[0] = 0; fib[1] = 1;
for (int i = 2; i < 100; i++)
    fib[i] = (fib[i-1] + fib[i-2]) % 10007;  // 只保留余数,防止溢出

✅ 判断整除

如果 a % b == 0,说明 a 能被 b 整除。比如判断一个数是不是3的倍数:if (n % 3 == 0)


4. 新手容易犯的常见错误

❌ 错误1:除数写成0

int a = 10, b = 0;
int c = a % b;  // 运行时错误!程序崩溃

正确做法:写代码时确保除数不为0,或者先判断再计算。

❌ 错误2:对浮点数用 %

double x = 5.5;
int y = x % 2;   // 编译错误:%不能用于浮点数

正确做法:先把浮点数转换成整数(比如强制类型转换),或者用数学函数 fmod (C++标准库提供)。

❌ 错误3:忘记运算符优先级

%*/ 是同级的,从左到右计算。比如:

int ans = 10 + 20 % 3;  // 先算20%3=2,再算10+2=12,不是(10+20)%3=0

正确做法:如果不确定,就加括号 (10+20)%3

❌ 错误4:误解取余范围

正数取余结果在 0b-1 之间,但很多人以为结果可以是负数。C++中负数取余可能为负(如 -5 % 3 = -2),建议初学只用正数运算


5. 完整示例:计算未来是星期几

假设今天是星期一(我们把星期一当作1,星期二2,……星期日7)。输入过几天,输出那天是星期几。

#include <iostream>
using namespace std;

int main() {
    int today = 1;         // 今天是星期一
    int days;              // 过几天
    cout << "请输入天数: ";
    cin >> days;

    // 注意:星期几是1~7,不能用days%7直接得结果
    // 计算方法:(today + days - 1) % 7 + 1
    // 减1再+1是为了把余数0~6映射到1~7
    int future = (today + days - 1) % 7 + 1;

    cout << "过了" << days << "天后是星期" << future << endl;

    // 保持原有内容:奇偶判断
    int n;
    cout << "再输入一个整数,判断奇偶: ";
    cin >> n;
    if (n % 2 == 0)
        cout << n << "是偶数" << endl;
    else
        cout << n << "是奇数" << endl;

    // 取个位数
    cout << "个位数是: " << n % 10 << endl;

    // 维持原有闰年判断
    int year = 2024;
    if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
        cout << year << "是闰年" << endl;
    else
        cout << year << "不是闰年" << endl;

    return 0;
}

运行结果示例:

请输入天数: 10
过了10天后是星期4
再输入一个整数,判断奇偶: 7
7是奇数
个位数是: 7
2024是闰年

6. 相关知识点指引

学完取余,你可以继续探索这些有意思的内容:

  • 快速幂与模运算:计算大数幂(比如 2^100 mod 1007),避免直接乘爆。
  • 同余方程:判断两个数模某个数相等,例如 a ≡ b (mod m)
  • 最大公约数与模逆元:用辗转相除法求gcd,以及模运算下的“除法”。
  • 循环队列:用取余实现数组的环形使用,像游戏中的轮换。

记住:取余就像在数轴上绕圈子——每次加一圈,你又回到了熟悉的地方。多动手写代码,很快就能掌握!

例题精讲

1单选题

在C++中,表达式 (-7) % 3 的结果是?

A-1
B1
C-2
D2
2判断题

若 a % b == 0 成立(b≠0),则 a 一定能被 b 整除。

3填空题
以下程序输入一个正整数 n,输出 n 除以 3 的余数。请补全代码。
#include <iostream>
using namespace std;
int main() {
    int n;
    cin >> n;
    cout << ___ << endl;
    return 0;
}
4单选题

已知 m 为正整数,下列哪个表达式在C++中恒成立(对所有整数 a, b)?

A(a + b) % m == (a % m) + (b % m)
B(a + b) % m == (a % m + b % m) % m
C(a * b) % m == (a % m) * (b % m)
D(a - b) % m == (a % m) - (b % m)
5填空题
以下程序读入 n 个整数,输出它们之和除以 100 的余数。请补全循环体中的语句。
#include <iostream>
using namespace std;
int main() {
    int n, x, sum = 0;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> x;
        ___;
    }
    cout << sum % 100 << endl;
    return 0;
}