C语言编程实战:每日刷题 - day3

🌈这里是say-fall分享,感兴趣欢迎三连与评论区留言
🔥专栏:《C语言从零开始到精通》
《C语言编程实战》
《数据结构与算法》
《小游戏与项目》
💪格言:做好你自己,你才能吸引更多人,并与他们共赢,这才是你最好的成长方式。
前言:
本系列博客将会更新一些每日的刷题代码以及对题目的解读,帮助大家更好地理解编程思路和实现方法
文章目录
- 前言:
- 题目:
- 1. n的阶乘
- 2. 打印一个整数的每一位
- 3. 交换两个变量(不创建临时变量)
- 4. 统计二进制中1的个数
- 5. 求两个数二进制中不同位的个数
- 6. 单身狗1
- 7. 打印整数二进制的奇数位和偶数位
- 知识点
- 1. 右移操作符
- 1.1. 逻辑右移(Logical Right Shift)
- 1.2 算术右移(Arithmetic Right Shift)
- 1.3 关键区别总结
- 1.4 C 语言中的右移规则
题目:
1. n的阶乘
- 递归和非递归分别实现求n的阶乘(不考虑溢出的问题)
阶乘的定义是n! = n × (n-1) × … × 1,其中0!和1!都为1。下面分别用递归和循环(非递归)的方式实现:
//递归和非递归分别实现求n的阶乘(不考虑溢出的问题)
#include<stdio.h>
//递归实现:将大问题分解为规模更小的子问题,直到达到终止条件
int fact(int n)
{//终止条件:1的阶乘是1,2的阶乘是2(也可简化为n<=1时返回1)if (n == 1 || n == 2){return n;}else{//递归公式:n! = n × (n-1)!return n * fact(n - 1);}
}int main()
{//测试递归实现,计算6的阶乘printf("%d\n", fact(6));//循环(非递归)实现:通过迭代累乘得到结果int n = 6;int facter = 1; //初始化乘积为1(乘法的单位元)while (n > 0){facter *= n; //累乘当前值n--; //逐步减小数值,直到n变为0}printf("%d\n", facter);return 0;
}
2. 打印一个整数的每一位
- 递归方式实现打印一个整数的每一位
要打印一个整数的每一位(如951打印为9 5 1),可以通过递归不断提取高位数字,直到剩下最后一位时开始打印,从而实现从高位到低位的输出。
//递归方式实现打印一个整数的每一位
#include<stdio.h>
//递归实现:先处理高位,再打印当前位
void print(int n)
{//终止条件:如果n是个位数,直接打印if (n < 10){printf("%d ", n);}else{//先递归处理去掉最后一位的数(获取高位)print(n / 10);//再打印最后一位(通过取余10得到)printf("%d ", n % 10);}
}int main()
{//测试:打印951的每一位print(951); //输出结果:9 5 1 return 0;
}
3. 交换两个变量(不创建临时变量)
- 不允许创建临时变量,交换两个整数的内容
通常交换两个变量需要借助临时变量,这里利用异或()的特性实现无临时变量交换。异或的特点是:aa=0,a^0=a,且满足交换律和结合律。
#include <stdio.h>
int main()
{int a = 6;int b = 12;printf("交换前:a=%d b=%d\n", a, b);//第一步:a存储a和b的异或结果(a^b)a = a ^ b;//第二步:b = (a^b) ^ b = a ^ (b^b) = a ^ 0 = a(此时b的值变为原来的a)b = a ^ b;//第三步:a = (a^b) ^ a(新b是原a)= (原a^原b) ^ 原a = 原b ^ (原a^原a) = 原b ^ 0 = 原ba = a ^ b;printf("交换后:a=%d b=%d\n", a, b);return 0;
}
4. 统计二进制中1的个数
- 二进制中1的个数
统计二进制中1的个数可以利用位运算的技巧:n & (n-1) 会消除n二进制中最后一个1。通过循环执行该操作,直到n变为0,执行的次数就是1的个数。
//写一个函数返回参数二进制中 1 的个数。
//比如: 15 0000 1111 4 个 1
#include<stdio.h>
void Count(int n)
{int count = 0; //计数器,记录1的个数while (n != 0){//n & (n-1) 会消除n的二进制中最后一个1n = n & (n - 1);count++; //每消除一个1,计数器加1}printf("%d\n", count);
}int main()
{int n = 0;scanf("%d", &n); //输入一个整数Count(n); //统计并打印该整数二进制中1的个数return 0;
}
5. 求两个数二进制中不同位的个数
- 编程实现:两个int(32位)整数m和n的二进制表达中,有多少个位(bit)不同?
输入例子:
1999 2299
输出例子:7
关键思路是利用异或(^)操作:两个数异或后,结果的二进制中,1的位置就是两个数二进制不同的位置。因此,只需统计异或结果中1的个数即可。
#include <stdio.h>//统计一个数二进制中1的个数(复用第4题的逻辑)
int Count(int n)
{int count = 0;while (n != 0){n = n & (n - 1);count++;}return count;
}int main() {int a, b;//循环读取输入,直到文件结束(EOF)while (scanf("%d %d", &a, &b) != EOF) { //a^b的结果中,1的位置即为a和b二进制不同的位置int c = a ^ b;//统计c中1的个数,即为不同位的数量int count = Count(c);printf("%d\n", count);}return 0;
}
6. 单身狗1
- 在一个整型数组中,只有一个数字出现一次,其他数组都是成对出现的,请找出那个只出现一次的数字。
- 例如:
数组中有:1 2 3 4 5 1 2 3 4,只有5出现一次,其他数字都出现2次,找出5
这里使用双重循环的暴力解法:遍历每个元素,统计其在数组中出现的次数,次数为1的即为目标数字。(更优的解法可以利用异或特性:aa=0,0a=a,将所有元素异或后结果即为只出现一次的数字)
//在一个整型数组中,只有一个数字出现一次,其他数组都是成对出现的,请找出那个只出现一次的数字。
//数组中有:1 2 3 4 5 1 2 3 4,只有5出现一次,其他数字都出现2次,找出5
#include <stdio.h>
int main()
{int arr[] = { 1,2,3,4,5,5,4,6,3,2,1 }; //示例数组,6是只出现一次的数字int sz = sizeof(arr) / sizeof(arr[0]); //计算数组元素个数//遍历数组中的每个元素for (int i = 0;i < sz;i++){int count = 0; //统计当前元素出现的次数//再次遍历数组,计数与当前元素相等的元素个数for (int j = 0;j < sz;j++){if (arr[i] == arr[j]){count++;}}//如果次数为1,说明找到目标数字if (count == 1)printf("%d",arr[i]);}return 0;
}
7. 打印整数二进制的奇数位和偶数位
- 获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列
整数的二进制位从右向左(即从低位到高位)编号,最右边为第1位(奇数位),第2位为偶数位,以此类推。通过循环取余和除法提取每一位,分别存入奇数位和偶数位的数组,最后打印即可。
//获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列
#include <stdio.h>
void print(int n)
{int count = 0; //记录当前是第几位(从1开始)int arr1[16] = {0}; //存储奇数位(第1,3,5,...位),int型共32位,故最多16个奇数位int arr2[16] = {0}; //存储偶数位(第2,4,6,...位),最多16个偶数位while (n != 0){int m = n % 2; //提取当前最低位(0或1)count++; //位数加1//判断当前位是奇数位还是偶数位,存入对应数组if (count % 2 != 0) //奇数位(第1,3,5...位){arr1[(count - 1)/2] = m; //计算存储位置(0,1,2...)}else //偶数位(第2,4,6...位){arr2[count/2 -1] = m; //计算存储位置(0,1,2...)}n = n / 2; //移除已经处理的最低位}//打印奇数位(从右向左,即从第1位开始)printf("奇数位,从右向左依次:");for (int i = 0;i < 16; i++){printf("%d ", arr1[i]);}printf("\n");//打印偶数位(从右向左,即从第2位开始)printf("偶数位,从右向左依次:");for (int i = 0;i < 16; i++){printf("%d ", arr2[i]);}
}int main()
{print(0b1111); //测试二进制1111(十进制15),其奇数位为1,1,偶数位为1,1return 0;
}
知识点
1. 右移操作符
在计算机中,右移运算(>>)是对二进制位的操作,根据处理符号位的方式不同,分为逻辑右移和算术右移,二者的核心区别在于移位后左侧空出的位如何填充。下面详细讲解:
1.1. 逻辑右移(Logical Right Shift)
- 规则:不管原数是正数还是负数,右移时左侧空出的位一律用 0 填充,右侧超出边界的位直接丢弃。
- 适用场景:通常用于无符号整数(
unsigned),因为无符号数没有符号位,仅表示数值大小。 - 示例:
以 8 位二进制为例,对无符号数0b10110100(十进制 180)进行逻辑右移 2 位:
原二进制:10110100
右移 2 位后:00101101(左侧补 0,右侧丢弃最后 2 位)
结果为十进制45。
1.2 算术右移(Arithmetic Right Shift)
-
规则:右移时,左侧空出的位用原数的符号位(最高位)填充,右侧超出边界的位直接丢弃。
- 若原数是正数(符号位为 0),则左侧补 0;
- 若原数是负数(符号位为 1),则左侧补 1。
-
适用场景:主要用于有符号整数(
signed),目的是保持移位后数值的符号不变(即移位后与原数同号),且近似于“除以 2 的 n 次方”(向下取整)。 -
示例:
① 正数右移:8 位有符号数0b00110100(十进制 52)算术右移 2 位:
原二进制:0 0110100(符号位为 0)
右移 2 位后:0 0001101(左侧补符号位 0)
结果为十进制13(52 ÷ 4 = 13,符合预期)。② 负数右移:8 位有符号数
0b11001100(十进制 -52,补码表示)算术右移 2 位:
原二进制:1 1001100(符号位为 1)
右移 2 位后:1 1110011(左侧补符号位 1)
结果为十进制-13(-52 ÷ 4 = -13,符合预期)。
1.3 关键区别总结
| 类型 | 左侧填充值 | 适用数据类型 | 核心目的 |
|---|---|---|---|
| 逻辑右移 | 固定填充 0 | 无符号整数(unsigned) | 仅移动位,不考虑符号 |
| 算术右移 | 填充原符号位 | 有符号整数(signed) | 保持符号不变,近似除以 2ⁿ |
1.4 C 语言中的右移规则
C 语言标准没有明确规定有符号数的右移是逻辑右移还是算术右移,具体由编译器实现(大多数编译器对有符号数采用算术右移,对无符号数采用逻辑右移)。例如:
int a = -52; // 二进制补码:11111111 11111111 11111111 11001100(32位)
a = a >> 2; // 算术右移后:11111111 11111111 11111111 11110011(结果为 -13)unsigned int b = 180; // 二进制:00000000 00000000 00000000 10110100
b = b >> 2; // 逻辑右移后:00000000 00000000 00000000 00101101(结果为 45)
因此,在处理有符号数的右移时,需注意编译器的行为,避免依赖未定义的操作。
- 本节完…
