【左拥右抱——分支与循环语句练习(下篇)】

💖 技术宅,拯救世界!

🎁作者:@ecember
🎁专栏:《从0开始——C语言》
🎁 对读者的话:相信奇迹的人本身和奇迹一样伟大

Alt


🌹感谢大家的点赞关注 🌹,如果有需要可以看我主页专栏哟💖


⚡6. 前言

种一棵树最好的时间是十年前,其次是现在。大家好,我们接着上篇博客的内容继续学习分支与循环语句的内容。上一篇博客链接(左拥右抱——分支与循环语句(上篇)),以下内容均在vs2022中编译哦。💖
在这里插入图片描述

⚡7. 分支与循环结构杂烩练习

这里我给大家整理了分支循环的几道经典题目,题目虽少,但全是干货哟!

🌠7.1 计算n的阶乘

我们从键盘上输入一个整型数字n,编写程序计算数字n的阶乘。数字n的阶乘实际上就是n* (n-1)* (n-2) * …*2 *1,每一位依次递减,这是我们的for循环就派上用场了,我们倒过来算更加容易理解,即计算1* 2 * …*n

int main()
{
	int n;
	int i = 0, sum = 1;//定义sum存放阶乘的值
	scanf("%d", &n);

	for (i = 1; i <= n; i++)
	{
		sum *= i;
	}

	printf("%d\n", sum);

	return 0;
}

我们随意输入10和15。
在这里插入图片描述
在这里插入图片描述

注意这里sum初始化值必须为1,因为如果初始化为0,后续乘积全部0。这里我们试想如果输入一个比较大的数呢?比如50
在这里插入图片描述
这是为什么呢?我们试想50的阶乘是个多么大的数字,我们的int类型还能存放得下吗?我们这里科普一个INT_MAX的概念,其表示int类型最大能存放的值,我们在编译器上输入该关键字(注意需要引入头文件<limits.h>),并转到定义
在这里插入图片描述
这表明int类型最大存放的值,而50的阶乘远远超过了这个数,故结果会出现错误。

🌠7.2 计算1-n各个数字阶乘之和

这里我们要计算各个数字阶乘之和,就相当于上一道题的升级版,现在我们数字n的阶乘已经会求啦,剩下的就是要把连续的阶乘相加,我们试想1到100的数字之和是不是如此简单,一个循环就OK啦,这里我们把1-100各个数字当成阶乘,在外部嵌套一个循环就大功告成啦。

int main()
{
	int n = 0;
	//产生1~n的数字
	int i = 0, j = 0;
	int ret = 1;
	int sum = 0;
	scanf("%d", &n);
	//1!+2!+3! = 1+2+6 = 9
	for (i = 1; i <= n; i++)
	{
		ret = 1;
		for (j = 1; j <= i; j++)
		{
			ret = ret * j;
		}
		sum = sum + ret;
	}
	
	printf("%d\n", sum);//9

	return 0;
}

我们试一下1和4。
在这里插入图片描述

在这里插入图片描述

这里的ret = 1为什么要写在循环里面呢,直接写在外面不行吗?我们试想,如果写在外面,那么每次求完阶乘后ret的值就会保留,后续就不是从1开始阶乘,会导致程序出现bug,所以我们每求完一次阶乘就需要对ret重新初始化为1

那还有没有更优质的算法呢?这里的for嵌套循环次数偏高,程序效率太低,运行时间长,一般不会实用,我们就需要在循环次数上下功夫,前面我们说道如果每次不初始化ret为1,那么每次阶乘的数据就会自动保留下来,那我们是不是可以不用外层嵌套循环,直接使用ret每次保留下来的数据来进行循环呢?即每次只在ret后乘以下一个数字,即为下一个数字的阶乘(n * (n-1)!是不是就是n! )。那么我们便有了思路。

int main()
{
	int n;
	int i = 0, sum1 = 1, sum2 = 0;
    
	scanf("%d", &n);

	for (i = 1; i <= n; i++)
	{
		sum1 *= i;
		sum2 += sum1;
	}

	printf("%d\n", sum2);

	return 0;
}

验证一波。
在这里插入图片描述
在这里插入图片描述

这个算法也就是我们说的优质算法,循环次数整整比之前那个算法少了整整一个数量级,算法效率大大提高。

🌠 7.3 二分查找

我们常说的二分查找其实就是在有序数组中查找指定数字n,说到这个可能友友们会想到直接一个for循环将整个数组遍历一遍即可,这个方法当然可行,代码也简单,但是效率是不是会偏低,我们这里介绍一种效率比较高的算法——二分查找

我们以数组arr为例里面存放1-10总共十个元素。
在这里插入图片描述

我们这里令下标 min = 0, max = 9。中间值为mid,如果我们要查找元素6,对应的下标为5,即查找arr[ 5 ]。这里我们观察到第一次查找发现arr[ mid ] < arr[5],则最小值后移min = mid + 1,max不变。
在这里插入图片描述

这里我们再次使用二分查找,发现arr[ mid ] > arr[ 5 ],即最大值前移max = mid - 1,min不变。
在这里插入图片描述
当我们再次查找时,发现arr[ mid ] = arr[ 5 ],即查找成功。
在这里插入图片描述
当我们查找的值并不存在这个数组中时,会出现什么情况呢?最后min,max的状态是什么呢?——由于min和max是步步逼近的即(当min = max时即表示查找完毕,如果这时还没找到元素则表示元素查找失败),所以我们的while循环条件是不是有了 ( min <= max)。好了,分析完毕上代码。

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int k = 6;
	int sz = sizeof(arr) / sizeof(arr[0]);

	int min = 0;
	int max = sz - 1;

	while (min <= max)
	{
		int mid = (min + max) / 2;

		if (k > arr[mid])
		{
			min = mid + 1;
		}
		else if (k < arr[mid])
		{
			max = mid - 1;
		}
		else
		{
			printf("已找到,下标为: %d\n", mid);
			break;
		}
	}
	if (min > max)
	{
		printf("找不到了\n");
	}

	return 0;
}

我们来试试结果。(查找值k = 6时)
在这里插入图片描述
这里我们分析分析mid的求解方式合不合理呢?这里的min = 0表示合理的,但我们想要求单纯两数平均值时,可能就不合理了,因为如果我们两数相加正好超过了INT_MAX ,而两数平均值正好又没超过这个范围,那么这时以这种方式算出的平均值就是有问题的,那么该怎么解决呢?
在这里插入图片描述
我们用较大的那部分减去较小的那部分除以二,然后再加上小的那部分即可求出平均值即(mid = min + (max - min)/ 2),这样就不会出现数值溢出的问题。好了我们再优化一遍算法加入输入值。代码如下:

#define MAX 100
int main()
{
	int min = 0, max = 0, mid = 0;
	int i, j = 0;
	int arr[MAX] = { 0 };
	int n;
	printf("请输入数组区间:\n");
	scanf("%d %d", &min, &max);
	printf("请输入待查找值:");
	scanf("%d", &n);
	for (i = min; i <= max; i++)
	{
		arr[j] = i;
		j++;
	}
	max = j - 1;
	min = 0;
	while (min <= max)
	{
		mid = min + (max - min) / 2;
		
		if (n < arr[mid])
		{
			max = mid - 1;
		}
		else if (n > arr[mid])
		{
			min = mid + 1;
		}
		else
		{
			printf("已找到,下标为:%d\n", mid);
			break;
		}
	}
	if (min > max)
	{
		printf("未查找到该值\n");
	}

	return 0;
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
测试结果正确!

🌠 7.4 演示多个字符从两端移动,向中间汇聚

题目的意思是实现打印一段字符,使其慢慢打印如图所示
在这里插入图片描述

在此之前,先来个小科普。

小科普

Sleep函数——程序休眠,例:Sleep(1000)——程序休眠1s(需引入头文件#include <windows.h>
system函数——执行系统指令,例:system(“cls”)——清屏,system(“shutdown -s -t 180”)——关机指令,system(“shutdown -a”)——取消关机指令(需引入头文件#include <stdlib.h>

好了,开始我们的正题,既然我们需要逐步的打印,相当于数组第一个元素和最后一个元素先打印,而后打印第二个和倒数第二个元素,这里我们是不是会想到可以用循环结构实现数组下标的移动,那么使先打印出来的中间位置都为‘#’呢?——定义两个数组即可,然后数组数据交换覆盖即可,定义一个全为“#”的数组,然后用目标数组逐值覆盖即可

int main()
{
	int i = 0, j = 0;
	char arr1[] = "hello bit";
	char arr2[] = "#########";
	j = sizeof(arr1) / sizeof(arr1[0]) - 2;
	//j = strlen(arr1) - 1;
	for (i = 0; i <= j; i++, j--)
	{
		arr2[i] = arr1[i];
		arr2[j] = arr1[j];
		printf("%s\n", arr2);
		Sleep(1000);//屏幕停留一秒 1000ms
		system("cls");//清屏
		//都需要引入头文件
	}
	printf("%s\n", arr2);

	return 0;
}

这里需要注意的是循环次数j的设置,我们通过前面的学习知道用上述方式定义数组的话,数组末尾会涵盖一个‘\0’,所以数组数据涵盖时要从最后一位’\0’的前一位数据开始交换,故用sizeof求出的数组长度需减2,而用strlen求出的长度只需要减1(strlen不计入‘\0’)。

⚡8.分支与循环小程序

🌠 8.1 密码匹配(限定3次)

在现实生活中,我们会遇到很多输入密码的程序,这里给大家简易密码匹配程序,密码我们知道是一串字符串,要实现字符串的匹配我们这里介绍一个函数strcmp(引入头文件<string.h>),此函数可以实现字符串的匹配,现在有了匹配函数,我们知道要限定3次匹配,不成功就退出,这也说明了循环次数为3

int main()
{
	int i = 0;
	char password[20] = { 0 };
	for (i = 0; i < 3; i++)
	{
		printf("请输入密码:\n");
		scanf("%s", password);
		if (strcmp(password, "abcdef") == 0)//strcmp函数使用方法,密码为“abcdef”
		{
			printf("密码正确\n");
			system("cls");
			printf("密码正确\n");
			break;
		}
		else
		{
			printf("密码错误,重新输入");
			Sleep(1000);
			system("cls");
		}
	}
	
	if (i == 3)
	{
		printf("三次密码均输入错误,退出程序\n");
	}
	return 0;
}

这里我们定义了一个数组password来存放用户输入的密码,当密码成功时,break跳出程序,匹配失败则返回继续匹配直到三次。

🌠 8.2 猜数字游戏

游戏规则:
1.电脑随机生成一个一个数字(1-100)。
2.玩家猜数字。
3.玩家猜小了就告知猜小了,猜大了就告知猜大了。
4.直到猜对为止。
5.游戏可以一直玩。

在书写代码之前我们先来小科普一下。

小科普

1. rand函数
rand()函数可以返回随机值,我们可以通过RAND_MAX查看返回随机数的范围为0—32767(16进制为0x7fff)。
在这里插入图片描述
如果把rand()函数叫做随机数发生的车间,那么srand就是车间工作的电源,因为它的功能是播下随机数发生器种子。我们不妨在网站上搜一搜srand具体功能
在这里插入图片描述
这里的srand内参数的值决定了随机数发生器rand生成的值,当srand内为随机值时,函数rand返回的才是随机值。我们不妨用代码验证一番。

int main()
{
	int ret = rand();
	printf("%d\n", ret);

	return 0;
}

在这里插入图片描述
我们要通过验证发现每运行一次,打印出来的值都是一样的(友友们的打印出来可能不是41,但无论打印几次结果都和第一次一样),这还叫什么随机值呢?——足以可见srand函数的重要性,只有srand函数内为随机值,rand()返回值才为随机值,那么怎么才能保证srand内为随机值呢?

2. 时间戳
我们知道我们的时间在一分一秒的流失,无时无刻都在变化,那么我们的时间是不是就可以作为srand内的值呢?——当然可以。时间戳就表示了此时变化的时间,因此C语言引入了time函数,返回值就是我们电脑的时间,我们不妨在网站上再搜一遍time函数。
在这里插入图片描述
注意我们srand函数内值为unsigned int,故需要对time进行强制转换,这里我们的随机数发生器种子已经有了雏形 srand((unsigned int)time(NULL) );注意time后需跟一个空指针NULL。

我们也可以来检验一番。

int main()
{
	srand((unsigned int)time(NULL));
	int ret = rand();
	printf("%d\n", ret);

	return 0;
}

第一次运行。
在这里插入图片描述
第二次运行。
在这里插入图片描述
第三次运行。
在这里插入图片描述


好了,我们科普结束回到正题,现在开始做我们的猜数字游戏

一.界面设置

void menu()
{
	printf("******************\n");
	printf("******1.play******\n");
	printf("******0.exit******\n");
	printf("******************\n");
	printf("\n");

}

二.判定函数设置

void game()
{
	int guess = 0;
	//1.生成一个随机数
	
	int ret = rand() % 100 + 1; //余数只可能是1-100的数字
	
	//2.猜数字

	while (1)
	{
		printf("猜数字:>");
		scanf("%d", &guess);
		if (guess < ret)
		{
			printf("猜小了\n");
		}
		else if (guess > ret)
		{
			printf("猜大了\n");
			continue;
		}
		else
		{
			printf("恭喜你,猜对了!\n");
			break;
		}
	}

}

三.main函数设置

int main()
{
	int input = 0;
	//放在此处即可保证生成的一定是随机值不会重复
	srand((unsigned int)time(NULL));//类型转换->时间戳返回值/srand返回值置为一样

	do
	{
		menu();
		printf("请选择(1 / 0):>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			Sleep(2000);
			system("cls");
			printf("继续游戏:\n");
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default :
			printf("输入错误,请重新输入(1 / 0)");
			Sleep(1500);
			system("cls");
			break;
		}
	} while (input);

	return 0;
}

四.程序试运行
在这里插入图片描述
猜对之后程序会停1s后自动清屏,进入下一页。
在这里插入图片描述
这里选择1的话能继续猜,选择0即可退出游戏。

这里需注意我们的随机数必须是1-100之间的数,如生成的随机数ret(范围0-32767)必须%100(范围0-99)再加1(范围1-100)。

🌠 8.3 关机程序

这里我们搞一个关机程序,首先我们在电脑左下角搜索框中输入cmd,进去后输入shutdown -s-t 180,惊不惊喜,意不意外?别慌别慌,再次在键盘上输入shutdown -a即可关闭关机程序,开个玩笑,大家不要当真🌹,此处的两处代码即为C语言中能用的关机代码。我们前面给大家科普过,那咱现在就来写个C语言关机程序。

一.主体函数

int main()
{
	system("shutdown -s -t 180");
again:

	printf("请注意,你的电脑将在三分钟内关机。\n");
	printf("**************提示**************\n");
	printf("如果输入:我是猪,就取消关机\n\n");

	//判断
	char input[20] = { 0 };
	printf("请输入:>");
	scanf("%s", &input);
	if (strcmp(input, "我是猪") == 0)
	{
		system("shutdown -a");//取消关机
		printf("取消关机成功\n");
	}
	else
	{
		Sleep(500);
		system("cls");
		printf("指令错误,请再次输入:\n\n");
		goto again;
	}

	return 0;
}

二.代码试运行
在这里插入图片描述
倔强的我挣扎了一会。
在这里插入图片描述
然而最终我还是屈服了。

⚡9. 结语

好了,到这我们【左拥右抱——分支与循环语句练习(下篇)】就结束啦,后续我还会继续更新C语言的有关内容(函数)。学习永无止境,就会永远只会留给有准备的人。希望我的博客对大家有所帮助,如果喜欢的可点赞+收藏哦,也随时欢迎大家在评论区及时指出我的错误。
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

guaabd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值