C语言小课堂:模运算

模运算,又称模数、取模、取余数运算等,它得出一个数除以另一个数的余数。今天我们来一起了解一下在C语言中如何进行模运算。

一、知识点讲解

  1. 模运算是什么?

在C语言中,模运算(Modulo Operation)使用百分号 % 表示。它是一种二元运算符,用于计算两个整数相除后的余数。例如,a % b 表示 a 除以 b 的余数。

模运算的定义如下: 对于整数 a 和非零整数 ba % b 的结果 r 满足 a = q * b + r ,其中 q 是商,r 是余数。余数 r 的符号与被除数 a 的符号相同。其绝对值小于除数 b 的绝对值,即 0 <= |r| < |b|

示例:

10 % 3 的结果是 1 ,因为 10 = 3 * 3 + 1

-10 % 3 的结果是 -1 ,因为 -10 = -4 * 3 + 2,但C语言中,余数的符号与被除数相同,所以 -10 = -3 * 3 - 1,或者更准确地理解为 -10 / 3 的商为 -3,余数为 -1

10 % -3 的结果是 1 ,因为 10 = -3 * (-3) + 1

-10 % -3 的结果是 -1,因为 -10 = -3 * 3 - 1

  1. 何时用?

模运算在编程中具有广泛的应用,尤其是在处理周期性、循环性或需要对数据进行分组、散列的场景。

何时用:

判断奇偶性: 当一个数 n2 进行模运算时,如果结果为 0 ,则 n 是偶数;如果结果为 1-1(取决于 n 的符号),则 n 是奇数。

限制数值范围(循环计数): 当需要一个变量在一个固定范围内循环时,模运算非常有用。例如,一个时钟的秒数从 059 循环,可以使用 (current_second + 1) % 60 来计算下一秒。

哈希函数: 在数据结构和算法中,模运算常用于构建哈希函数,将任意大的键映射到固定大小的哈希表索引中。

数字位操作: 获取一个数字的个位数、十位数等。例如 n % 10 可以得到 n 的个位数。

周期性任务调度: 在操作系统或嵌入式系统中,周期性任务的调度常常涉及模运算,以确定任务在某个时间周期内的执行点。

加密算法: 一些加密算法(如RSA)中会用到模幂运算。

为什么用: 模运算提供了一种简洁高效的方式来处理整数除法后的余数,这对于解决上述问题至关重要。它允许我们实现周期性行为、数据分组和边界限制,而无需复杂的条件判断或循环结构。

与关联知识点的联系与区别:

与除法运算符 / 的联系: 模运算与除法运算是紧密相关的。a / b 给出的是商,而 a % b 给出的是余数。两者共同构成了整数除法的完整结果。

与位运算符的联系: 对于 2 的幂次的模运算,有时可以用位运算代替以提高效率。例如,n % 2 等价于 n & 1(对于非负数)。然而,位运算只适用于除数为 2 的幂次的情况,而模运算则适用于任意非零整数。

  1. 怎么用?

模运算的用法相对简单,但需要注意其操作数的类型和符号。

基本语法:result = operand1 % operand2;

其中 operand1operand2 必须是整数类型(int , long , short 等)。如果操作数是浮点类型,编译器会报错。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>

int main() {
int num1 = 17;
int num2 = 5;
int num3 = -17;
int num4 = -5;

// 示例 1: 正数模正数
int result1 = num1 % num2; // 17 % 5 = 2
printf("%d %% %d = %d\n", num1, num2, result1);

// 示例 2: 负数模正数
int result2 = num3 % num2; // -17 % 5 = -2 (余数符号与被除数相同)
printf("%d %% %d = %d\n", num3, num2, result2);

// 示例 3: 正数模负数
int result3 = num1 % num4; // 17 % -5 = 2 (余数符号与被除数相同)
printf("%d %% %d = %d\n", num1, num4, result3);

// 示例 4: 负数模负数
int result4 = num3 % num4; // -17 % -5 = -2 (余数符号与被除数相同)
printf("%d %% %d = %d\n", num3, num4, result4);

// 示例 5: 判断奇偶性
int check_num = 24;
if (check_num % 2 == 0) {
printf("%d 是偶数。\n", check_num);
} else {
printf("%d 是奇数。\n", check_num);
}

// 示例 6: 循环计数(模拟时钟秒数)
int current_second = 58;
for (int i = 0; i < 5; i++) {
current_second = (current_second + 1) % 60;
printf("当前秒数: %d\n", current_second);
}

return 0;
}

注意事项:

除数( operand2 )不能为 0,否则会导致运行时错误(除零错误)。

模运算结果的符号与被除数(operand1)的符号相同。这是C语言标准规定的行为。

二、典型习题

  1. 选择题

一个整数 num7 进行模运算,结果可能是以下哪个值?

A. 7

B. -7

C. 5

D. 8

  1. 判断题

表达式 (15 % 4) == (-15 % -4) 的结果为真(true)。

A. 对

B. 错

  1. 填空题

请填写 num % 10 表达式的计算结果,使其完成获取一个两位数 num 的个位数字的任务。例如,当 num42 时,结果为 2

  1. 编程题

编写一个C语言程序,接收用户输入的一个正整数 n ,然后判断 n 是否为水仙花数。水仙花数是指一个三位数,其各位数字的立方和等于该数本身。例如, 153 是一个水仙花数,因为 1^3 + 5^3 + 3^3 = 1 + 125 + 27 = 153

要求:

程序应提示用户输入一个三位数。

使用模运算和除法运算分离出该三位数的百位、十位和个位。

计算各位数字的立方和。

判断并输出该数是否为水仙花数。

  1. 编程题

编写一个C语言程序,模拟一个简易的数字加密器。程序接收一个四位数的整数作为明文,然后对其进行加密。加密规则如下:

将明文的每个数字(个、十、百、千位)都加上 7

将每个数字的结果都对 10 取模,得到新的数字。

将新数字的千位和十位互换,百位和个位互换。

要求:

程序应提示用户输入一个四位数的整数。

使用模运算和除法运算分离出明文的各个数字。

按照加密规则进行计算和交换。

输出加密后的四位整数。

三、习题讲解

  1. 选择题

一个整数 num7 进行模运算,结果可能是以下哪个值?

A. 7

B. -7

C. 5

D. 8

答案:C

解析:模运算 a % b 的结果 r 满足 0 <= |r| < |b| ,且 r 的符号与被除数 a 的符号相同。 在这里,除数是 7,所以结果的绝对值必须小于 7

A. 7 的绝对值不小于 7

B. -7 的绝对值不小于 7

C. 5 的绝对值小于 7,且可以是正数,符合条件。

D. 8 的绝对值不小于 7 。 因此, 5 是唯一可能的选项。

  1. 判断题

表达式 (15 % 4) == (-15 % -4) 的结果为真(true)。

A. 对

B. 错

答案:B

解析:首先计算 15 % 415 = 3 * 4 + 3,所以 15 % 4 的结果是 3

然后计算 -15 % -4: C语言规定模运算结果的符号与被除数相同。-15 / -4 的商是 3 ,余数是 -3 (因为 -15 = 3 * (-4) - 3)。 所以 -15 % -4 的结果是 -3

比较 3 == -3,结果为假(false)。因此,原表达式的结果为

  1. 填空题

请填写 num % 10 表达式的计算结果,使其完成获取一个两位数 num 的个位数字的任务。例如,当 num42 时,结果为 2

答案: num % 10

解析:任何整数对 10 进行模运算,其结果就是该整数的个位数字。 例如:

42 % 10 = 2

123 % 10 = 3

7 % 10 = 7这是因为当一个数被 10 除时,商是去掉个位数后的部分,余数就是个位数本身。

  1. 编程题

编写一个C语言程序,接收用户输入的一个正整数 n ,然后判断 n 是否为水仙花数。水仙花数是指一个三位数,其各位数字的立方和等于该数本身。例如, 153 是一个水仙花数,因为 1^3 + 5^3 + 3^3 = 1 + 125 + 27 = 153

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
#include <math.h> // 需要用到pow函数计算立方,也可以手动乘三次

int main() {
int n, original_n;
int digit1, digit2, digit3; // 个位、十位、百位
int sum_of_cubes;

printf("请输入一个三位数: ");
scanf("%d", &n);

// 检查输入是否为三位数
if (n < 100 || n > 999) {
printf("输入不是一个三位数。\n");
return 1; // 退出程序
}

original_n = n; // 保存原始值,用于最后比较

// 分离个位
digit1 = n % 10;
n = n / 10; // 去掉个位

// 分离十位
digit2 = n % 10;
n = n / 10; // 去掉十位

// 分离百位
digit3 = n % 10; // 此时n只剩百位

// 计算各位数字的立方和
sum_of_cubes = (digit1 * digit1 * digit1) +
(digit2 * digit2 * digit2) +
(digit3 * digit3 * digit3);

// 也可以使用 pow 函数:
// sum_of_cubes = pow(digit1, 3) + pow(digit2, 3) + pow(digit3, 3);

// 判断是否为水仙花数
if (sum_of_cubes == original_n) {
printf("%d 是水仙花数。\n", original_n);
} else {
printf("%d 不是水仙花数。\n", original_n);
}

return 0;
}

解题思路:

输入与校验: 首先获取用户输入,并简单校验是否为三位数,以确保后续分离数字的逻辑正确。

分离数字:

个位: 利用 n % 10 可以直接得到 n 的个位数字。

去除个位: 利用 n = n / 10 可以将 n 的个位去除,使其变成一个两位数(或一位数)。

十位和百位: 重复上述过程,对新的 n 进行 n % 10 得到十位,再 n = n / 10 得到百位。

计算立方和: 将分离出的三个数字分别求立方,然后相加。

判断: 将计算出的立方和与原始输入的数进行比较,如果相等,则是水仙花数。

  1. 编程题

编写一个C语言程序,模拟一个简易的数字加密器。程序接收一个四位数的整数作为明文,然后对其进行加密。加密规则如下:

将明文的每个数字(个、十、百、千位)都加上 7

将每个数字的结果都对 10 取模,得到新的数字。

将新数字的千位和十位互换,百位和个位互换。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>

int main() {
int plaintext;
int d1, d2, d3, d4; // 千位、百位、十位、个位
int e1, e2, e3, e4; // 加密后的数字
int ciphertext;

printf("请输入一个四位数的整数(明文):");
scanf("%d", &plaintext);

// 检查输入是否为四位数
if (plaintext < 1000 || plaintext > 9999) {
printf("输入不是一个四位数。\n");
return 1;
}

// 1. 分离明文的各个数字
d4 = plaintext % 10; // 个位
d3 = (plaintext / 10) % 10; // 十位
d2 = (plaintext / 100) % 10;// 百位
d1 = plaintext / 1000; // 千位

// 2. 每个数字都加上7,然后对10取模
e1 = (d1 + 7) % 10;
e2 = (d2 + 7) % 10;
e3 = (d3 + 7) % 10;
e4 = (d4 + 7) % 10;

// 3. 交换数字:千位和十位互换,百位和个位互换
// 原始:e1 e2 e3 e4 (千百十个)
// 交换后:e3 e4 e1 e2 (新的千百十个)
ciphertext = e3 * 1000 + e4 * 100 + e1 * 10 + e2;

printf("加密后的数字是:%d\n", ciphertext);

return 0;
}

解题思路:

输入与校验: 获取用户输入,并确保其为四位数。

分离数字: 利用除法和模运算的组合,从明文中逐一提取出千位、百位、十位和个位。

plaintext % 10 得到个位。

(plaintext / 10) % 10 得到十位。

(plaintext / 100) % 10 得到百位。

plaintext / 1000 得到千位。

加密计算: 对每个分离出的数字,按照规则 (digit + 7) % 10 进行计算,得到加密后的新数字。

数字交换与重组: 根据交换规则,将新得到的四个数字重新组合成一个四位数。需要注意的是,这里是位置的交换,而不是数字本身的交换。新数字的千位是原先的十位加密结果,新数字的百位是原先的个位加密结果,以此类推。

新的千位是 e3 (原十位加密结果)

新的百位是 e4 (原个位加密结果)

新的十位是 e1 (原千位加密结果)

新的个位是 e2 (原百位加密结果) 最后将它们乘以相应的权重(1000, 100, 10, 1)并相加。

你总以为时间线是笔直向前,却不曾料想,在某个不经意的节点,一切又回到了起点,那不是停滞,而是另一种周而复始的秩序,每一次归零,都蕴含着新的可能。