C语言编程实战:大家一起云刷题 - day3

C语言位运算与递归实战

在这里插入图片描述

🌈这里是say-fall分享,感兴趣欢迎三连与评论区留言
🔥专栏:《C语言从零开始到精通》
《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 = 原b
	a = 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的二进制中最后一个1
		n = 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,1
	return 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)

因此,在处理有符号数的右移时,需注意编译器的行为,避免依赖未定义的操作。


  • 本节完…
Delphi 12.3 作为一款面向 Windows 平台的集成开发环境,由 Embarcadero Technologies 负责其持续演进。该环境以 Object Pascal 语言为核心,并依托 Visual Component Library(VCL)框架,广泛应用于各类桌面软件、数据库系统及企业级解决方案的开发。在此生态中,Excel4Delphi 作为一个重要的社区开源项目,致力于搭建 Delphi 与 Microsoft Excel 之间的高效桥梁,使开发者能够在自研程序中直接调用 Excel 的文档处理、工作表管理、单元格操作及宏执行等功能。 该项目以库文件与组件包的形式提供,开发者将其集成至 Delphi 工程后,即可通过封装良好的接口实现对 Excel 的编程控制。具体功能涵盖创建与编辑工作簿、格式化单元格、批量导入导出数据,乃至执行内置公式与宏指令等高级操作。这一机制显著降低了在财务分析、报表自动生成、数据整理等场景中实现 Excel 功能集成的技术门槛,使开发者无需深入掌握 COM 编程或 Excel 底层 API 即可完成复杂任务。 使用 Excel4Delphi 需具备基础的 Delphi 编程知识,并对 Excel 对象模型有一定理解。实践中需注意不同 Excel 版本间的兼容性,并严格遵循项目文档进行环境配置与依赖部署。此外,操作过程中应遵循文件访问的最佳实践,例如确保目标文件未被独占锁定,并实施完整的异常处理机制,以防数据损毁或程序意外中断。 该项目的持续维护依赖于 Delphi 开发者社区的集体贡献,通过定期更新以适配新版开发环境与 Office 套件,并修复已发现的问。对于需要深度融合 Excel 功能的 Delphi 应用而言,Excel4Delphi 提供了经过充分测试的可靠代码基础,使开发团队能更专注于业务逻辑与用户体验的优化,从而提升整体开发效率与软件质量。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值