C生万物 | 分支和循环语句【内含众多经典案例】

举报
烽起黎明 发表于 2023/02/21 12:31:18 2023/02/21
【摘要】 C生万物第二篇——分支和循环语句,为您详细介绍C语言中的分支和循环语句,内含众多经典案例同步教学

在这里插入图片描述

看完上一篇文章,我们对C语言要学习的一个整体知识框架有了一个了解,接下去,我们将进入分支和循环语句的学习,Are you ready?

@TOC

一、什么是语句?

  • 首先我们来说说什么是语句,在C语言中呢,可以将语句分为以下五类

①表达式语句
②函数调用语句
③控制语句
④复合语句
⑤空语句

  • 本次我们主要来讲讲==控制语句==,控制语句就是用于控制程序执行的流程,以实现程序的各种结构方式,C语言是面向过程的,有三种结构方式【顺序结构、选择结构、循环结构】
  • 而上面所说的控制语句在C语言中主要是有9种,我将其分为以下三类:

第一类:分支语句【if语句、switch语句】
第二类:循环语句【do while语句、while语句、for语句】
第三类:转向语句【break语句、goto语句、continue语句、return语句】

说了这么多,接下来让我们先进入分支语句的学习

二、分支语句(选择结构)

1、if语句

单分支与多分支

有关if语句的使用我们在初始C语言的时候已经讲过,这里不做过多解释,先来看一下这段代码

  • 这里是定义了一个age年龄,然后去进行输入,若是输入的年龄 < 18,则打印【未成年】,若是 > 180,那就打印【成年】
  • 具体意思也就是一个判断执行一条语句,满足了这个条件,就进入这个if分支执行里面的代码,else的话就是另一个分支
  • 那有同学问了,难道一个分支只能执行一条语句吗?不可以执行多条?
int main(void)
{
	int age = 0;
	scanf("%d", &age);
	if (age < 18) 
		printf("未成年\n");
	else 
		printf("成年\n");
	return 0;
}
  • 我们再来看看下面这段代码,可以看到,我在两个if分支都加上了{}大括号,这是一个规定,若是你不加上这个大括号,那么这个if分支就只会执行一条语句就跳过了
int main(void)
{
	int age = 0;
	scanf("%d", &age);
	if (age < 18) {
		printf("未成年\n");
		printf("不可酗酒\n");
	}
	else {
		printf("成年\n");
		printf("适度饮酒\n");
	}
	return 0;
}

然后我们再来讲一下多分支结构

  • 什么叫多分支结构呢,刚才的话我们是只有if…else这两个分支,现在我们的场景不只是【未成年】和【成年】了,而是不同的年龄段为不同的身份,我们来看一下代码
int main(void)
{
	int age = 0;
	scanf("%d", &age);
	if (age > 0 && age < 18) {
		printf("少年\n");
	}
	else if (age >= 18 && age < 30) {
		printf("青年\n");
	}
	else if (age >= 30 && age < 45) {
		printf("壮年\n");
	}
	else if (age >= 45 && age < 55) {
		printf("中年\n");
	}
	else if (age >= 55 && age < 75) {
		printf("老年\n");
	}
	else {
		printf("老寿星\n");
	}
	return 0;
}
  • 从上述代码可以看出,不同的分支具有不同的结果,而不是像上那样只有if…else,现在我们输入数据试试看

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 那这个时候有同学问,为什么就可以进入这个分支呢,这个原理是什么?
  • 这就要说到我们的语句执行原理,也就是【真】与【假】

0表示假,非0表示真

  • 那有些学会C语言的同学就会想到一个东西【布尔类型】,就是true/false,这个的话是C99里面颁布的,在C里面用的还是比较少,C++里面可能多一些,有兴趣的同学可以去研究一下

悬空else

但是你真的认为if…else就那么容易?我们来看一个东西,叫做【悬空else】,这是一个大家在书写代码是经常会疏漏的地方:star:

  • 首先看下面这段代码,你认为下面会打印出什么内容呢?
int main(void)
{
	int a = 0;
	int b = 2;
	if (a == 1)
		if (b == 2)
			printf("hehe\n");
	else
		printf("haha\n");


	return 0;
}
  • 首先按照大家的思维理解一下,a刚开始为0,所以不会进入第一个分支,而会进入第二个分支,然后打印【haha】,真的是这样吗?我们运行一下试试
  • 可以看到,什么内容都没有被打印,这是为什么呢?

在这里插入图片描述

  • 这其实就要讲到我所要说的【悬空else】,什么是悬空else呢,也就是在双层嵌套的if语句下,没有加上{}大括号,这个时候if分支后的else就叫做悬空else
  • 这个悬空else就会产生分歧,到底是和内部的if匹配呢,还是和外部的if匹配呢?这里也是有规定的,对于悬空的else,优先和内部的else进行一匹配,所以当把我这个代码复制进VS编译器时,这个else就自动会进行一个缩进,和内部的if做一个匹配
  • 所以这就可以印证了为什么没有打印出来任何东西,因为无论是【haha】还是【hehe】,都是处于一个大if分支下,而这个大分支的第一层还没有进去,所以程序会直接结束
  • 我们的代码应该改成下面这样
int main(void)
{
	int a = 0;
	int b = 2;
	if (a == 1)
		if (b == 2)
			printf("hehe\n");
		else
			printf("haha\n");


	return 0;
}
  • 这样的话其实就很清楚了,这个else是和内部if匹配的,但是其实呢这样也不是很规范,最好是加上{}大括号,才能显得规范一些

if书写形式的对比

有关if语句的书写也是有讲究的,我们一起来看一下

  • 你认为下面两段代码那个是书写规范一些的
//代码1
if (condition) {
    return x;
}
return y;
//代码2
if(condition)
{
    return x;
}
else
{
 return y;
}
  • 答案是第二段,其实它们所要表达的意思都是一样的,这个condition就是条件。若是条件成立,则return x,若是不成立,则return y
  • 但是为什么说第二段代码更好地,并不是它写得更长一些,而是这个逻辑框架比较清晰,让阅读者看了一目了然

接下去我们再来看另外两段代码,你认为那段代码比较好一些呢

//代码3
int num = 1;
if(num == 5)
{
    printf("hehe\n");
}
//代码4
int num = 1;
if(5 == num)
{
    printf("hehe\n");
}
  • 答案也是第二段,那这时候就有同学疑问了【num == 5】是在判断这个num值是否为5,那么把这个5放在前面是什么意思呢
  • 这就是说到一个很多程序员都会犯的,也就是把这个判断写成了【num = 5】,变成了一个赋值操作,这其实就会为后期的程序开发买下一个巨大的Bug,这时你在调Bug的时候就会很痛苦,就是找不到哪里有错误,这个时候就去规范我们对于代码的书写
  • 最好是写成【5 == num】,为什么要这样写呢,你看我这个时候将判断等于号改成一个等于号,这个时候编译器就会报错,说【这个表达式必须是可修改的左值】,这其实是正确的,以大家的思维来说,都是讲一个值赋给一个变量,也不会将一个变量给到一个具体的值,也就是【5】这个常量
  • 但程序报出错误的时候程序员就会立马去排错,这个时候也就降低了Bug存在的风险

在这里插入图片描述

🔥炙手可热的阶段小训练🔥

学完了if语句,接下去让我们来做两道练习题巩固一些所学的知识吧

  • 大家可以自己去做一下,这里只给出代码

1、判断一个数是否为奇数

int main(void)
{
	int i = 1;
	while (i <= 100) {
		if (i % 2 == 1)
			printf("奇数\n");
		else
			printf("偶数\n");
	}
	return 0;
}

2. 输出1-100之间的奇数

//way1
int i = 1;
while (i <= 100) {
	if (i % 2 == 1)
		printf("%d\n", i);
	i++;
}
//way2
int main(void)
{
	int i = 1;
	while (i <= 100) {
		printf("%d\n", i);
		i+=2;
	}
	return 0;
}

2、switch语句

讲完了一种分支语句,接下来让我们继续学习下一种分支语句,也就是switch语句

  • 对于switch语句,常常用于多分支的情况,那有同学说,那个多分支不是if…else也可以实现吗,那为什么要用这个呢
  • 我们来看看下面这段代码,这是对于星期的一个判断
int main()
{
	int day = 0;
	scanf("%d", day);
	if(day == 1)
		printf("星期一\n");
	else if(day == 2)
		printf("星期二\n");
	else if (day == 3)
		printf("星期三\n");
	else if (day == 4)
		printf("星期四\n");
	else if (day == 5)
		printf("星期五\n");
	else if (day == 6)
		printf("星期六\n");
	else
		printf("星期天\n");
	return 0;
}
  • 但是你仔细看,不觉得这样去写这么一个判断很复杂吗,要写这么多else if,会有些繁琐,接下去我们来学习一下这个【switch语句】
  • 先来看一些格式
switch(整型表达式)
{
    语句项;
}
  • 那这个语句项是什么呢?
//是一些case语句:
//如下:
case 整形常量表达式:
    语句;

好,了解了这些后呢,我们使用【switch语句】来重写一下前面的那个星期判断

int main()
{
	int day = 0;
	scanf("%d", &day);
	switch (day)
	{
	case 1:
		printf("星期一\n");
	case 2:
		printf("星期二\n");
	case 3:
		printf("星期三\n");
	case 4:
		printf("星期四\n");
	case 5:
		printf("星期五\n");
	case 6:
		printf("星期六\n");
	case 7:
		printf("星期天\n");
	}
	return 0;
}
  • 今天是星期四,我们来输入4运行看看

在这里插入图片描述

  • 那这时候有些小伙伴就震惊了,这是什么鬼?我想要的只是星期四而已,但是五、六、天都被输出出来了。这个的话叫做【switch穿透】,大家不要怕,我们来分析一下:mag:

break

  • 然后我来说一下这是为什么,对于switch语句的话,有很多【case】分支,一个语句执行完之后,还是会往下一个语句执行,但是我们希望的是在case:4进去,然后打印完了这一句话后便跳出这个判断,不再执行下去了,那这要怎么实现呢?
  • 这时候应该用到一个我们在初始C语言里讲到的,叫做break,到执行到这一个break的时候,便会跳出,不会再往下执行了

我们加上这个break后试试

int main()
{
	int day = 0;
	scanf("%d", &day);
	switch (day)
	{
	case 1:
		printf("星期一\n");
		break;
	case 2:
		printf("星期二\n");
		break;
	case 3:
		printf("星期三\n");
		break;
	case 4:
		printf("星期四\n");
		break;
	case 5:
		printf("星期五\n");
		break;
	case 6:
		printf("星期六\n");
		break;
	case 7:
		printf("星期天\n");
		break;
	}
	return 0;
}

在这里插入图片描述

  • 可以看到,成功了🎇
  • 但这样子程序还是不完整,我们继续来看看

default 子句

在这里插入图片描述

  • 可以看到,我在这个地方输入了一个8,但是程序什么都没有输出,这是因为8没有进入任何一个switch分支,所以程序直接结束了。那应该怎么办呀!!!
  • 不要急,这个时候我们应该再增加一个default分支,作为默认分支,若是其他case子句都没有进入,则会进入这个【default】分支,我们来试试
default:
	printf("无此星期");
	break;
  • 可以看到,成功了

在这里插入图片描述

  • 那这个时候我们换一种思维,当多个条件满足的时候,执行同一个结果,也就是这样的需求

1、输入1-5,输出的是“weekday”;
2、输入6-7,输出“weekend”

  • 我们来实现一下代码
int main(void)
{
	int day = 0;
	scanf("%d", &day);
	switch (day)
	{
	case 1:
	case 2:
	case 3:
	case 4:
	case 5:
		printf("workday\n");
		break;
	case 6:
	case 7:
		printf("weekend\n");
		break;
	default:
		printf("invalid\n");
		break;
	}
	return 0;
}

注意事项:break千万别忘了,很重要,不然会穿透的

📰强大的面试题📰

以下是一道面试题,我们一起来看看

  • 你认为下面会输出多少呢❓
int main()
{
	int n = 1;
	int m = 2;
	switch (n)
	{
	case 1:
		m++;
	case 2:
		n++;
	case 3:
		switch (n)
		{//switch允许嵌套使用
		case 1:
			n++;
		case 2:
			m++;
			n++;
			break;
		}
	case 4:
		m++;
		break;
	default:
		break;
	}
	printf("m = %d, n = %d\n", m, n);
	return 0;
}

在这里插入图片描述

  • Are you Right? Let’s analyze it.
  • 看完我下面这一段实时分析,你就会懂了

在这里插入图片描述

三、循环语句

好,讲完了这个分支语句后呢,我们来说一说C语言中的循环语句

1、while循环

  • 看到下面的代码,我们if的判断改成while就构成了最基本的while循环
if(1)
	printf("hehe\n");
while (1)
	printf("hehe\n");
  • 然后就是我们在初始C语言中就讲到过的通过循环来打印1~10的数字,在while循环内部控制变量的循环条件,若是满足,则一直循环,直到这个循环变量超过边界为止
int i = 1;
while (i <= 10)
{
	printf("%d ", i);
	i++;
}

while语句中的break和continue

对于break和continue,我们之前在分支判断中也有提到过,那在循环中是怎样的呢,我们来看一下

  • 你认为下面这段代码会打印什么内容。break的话停止后期的所有的循环,直接终止循环
int i = 1;
while (i <= 10)
{
	if (i == 5)
		break;
	printf("%d ", i);
	i++;
}

在这里插入图片描述

  • 可以看到,因为当i 循环到5的时候,遇到了break,因此跳出了整个while循环,不会执行下面的printf打印语句以及i++了,所以只会打印到4

  • 然后我们再来看看continue
  • 对于continue的话,是终止本次循环,强制进入下一次循环
int i = 1;
while (i <= 10)
{
	if (i == 5)
		continue;		//强制进行下一次循环
	printf("%d ", i);
	i++;	//当i为5的时候永远执行不到这一句
}

在这里插入图片描述

  • 可以看到这个光标的值,停留在4的后面不动了,这是为什么呢?
  • 我们来分析一下

在这里插入图片描述

  • 可以看到,这其实是一个死循环,当i 加到5的时候,由于进入了continue,所以i不会进行一个递增,继而导致了这个死循环
  • 那这个代码应该怎么改才不会死循环呢?对,就是把这个i++放到if判断的前面
int i = 0;
while (i < 10)
{
	i++;
	if (i == 5)
		continue;		//强制进行下一次循环
	printf("%d ", i);
}

在这里插入图片描述

  • 看完了【break】和【continue】,接下去我们来总结一下

continue - >>某种条件成立时跳过后面的代码
break - >>某种条件成立时整个循环不执行

getchar()的巧妙运用

讲到while循环,我们来说说【getchar()】这个库函数的使用

int ch = getchar();
putchar(ch);

在这里插入图片描述
在这里插入图片描述

  • 上述图是运行结果以及cplusplus的资料
  • 对于getchar()的话是对键盘读入一个字符,然后可以通过putchar()输出。要注意到,这个getchar()的返回值是int,这个我们下面会讲到

这个时候有同学问了,这个getchar()和while循环有什么关系呢?

  • 我们来看看下面这段代码
int ch = 0;
while ((ch = getchar()) != EOF)
{
	putchar(ch);
}

在这里插入图片描述

  • 从运行结果可以看到,使用while循环,每当我们输入一行数据,知道按下回车,putchar()会将这一行数据原封不动地打印出来
  • 这里的【EOF】指的就是【End Of File】文件末尾的,循环的意思是==当这个输入没有到达文件末尾时,getchar()就会从键盘上不断地去读取数据,直到文件末尾即要换行为止==

那这个时候又有同学说,这个getchar()很厉害呀,竟然可以读一串数据

  • 但其实这个getchar()的使用是存在缺陷的,我们一起来看看
  • 这是一段确认密码的程序
int main(void)
{
	char password[20] = { 0 };
	printf("请输入密码:");
	scanf("%s", password);		
	printf("请确认密码(Y/N):");
	int ch = getchar();

	if (ch == 'Y') {
		printf("确认成功");
	}
	else {
		printf("确认失败");
	}
	return 0;
}

在这里插入图片描述

  • 可以看到,我在输入完密码然后换行后,但是还没有去确认密码,但是程序直接结束了,说【确认失败】,这是为什么呢?这个getchar()到底有没有执行?
  • 我们来看一下==计算机内部缓冲区读取数据的原理==

在这里插入图片描述

  • 从这张原理图可以很清楚地看出,scanf会从键盘上读取一些你输入的数据,但你不输入的时候,它就一直等,一直等,直等到你输入为止。
  • 但是这个scanf不会直接去取你这个键盘上输入的数据,在计算机中呢,是存在一个输入缓冲区的,但我们把这个数据从键盘上输入进去的时候,会先进入缓冲区,当我这个123456输入进去后,为了让数据进入到缓冲区,我还敲了一个回车,表示这个输入结束
  • 然后scanf进来到缓冲区看了,有没有数据我可以读的,这时候它看到有数据,它就读,直到读到\n为止。然后将这个数据给到password作为密码;此时getchar()也来读数据了,这个getchar()的工作原理其实和scanf()是一样的,就看缓冲区里有没有东西,然后看到,哟,有个【\n】,于是读取了这个数据,把它放到ch里面去了,那接着就去判断这个ch为‘Y’吗,很明显不为‘Y’,所以就进入了else,打印了【确认失败】,==这就是编译器没有等我们输入确认密码就直接结束程序的原因==

你明白了吗?

2、for循环

语法明细

看完了while循环,接下去我们来看看for循环,对于for循环大家一定不陌生,它是使用最多的循环语句

  • 那有同学问了,我们既然已经有这个while循环了,为什么还要for循环呢,让我们先来看一下for循环的语法
  • 表达式1为初始化部分,用于初始化循环变量的
  • 表达式2为条件判断部分,用于判断循环时候终止
  • 表达式3为调整部分,用于循环条件的调整
for(表达式1; 表达式2; 表达式3)
 循环语句;
  • 可以看到,for循环不像while循环那样,将三个表达式分散在整个程序的各处,对于for循环的话都是放在这一个小括号里进行,每个表达式使用分号进行分隔

在这里插入图片描述

  • 就像我们这里去打印1-10的数字,使用for循环的话就会很清晰直观,代码也比较简练
int main(void)
{
	for (int i = 1; i <= 10; ++i)
	{
		printf("%d ", i);
	}
	return 0;
}

在这里插入图片描述

  • 可以看到,一样是可以打印出来

for循环中的break和continue

在while循环中,我们有详细讲到过break和continue,但是在for循环中是如何使用的呢?我们一起来看看

int main(void)
{
	for (int i = 1; i <= 10; ++i)
	{
		if (i == 5)
			break;
		printf("%d ", i);
	}
	return 0;
}
  • 首先来看break,很清晰直观,循环从1开始,循环的边界是到10,i++进行一个递增,也就是i加到11的时候会跳出这个for循环,然后看循环体内部,当循环进入时若判断这个【i == 5】时,则执行break语句跳出循环;若没有到达5,则会打印这个i

在这里插入图片描述


  • 然后我们再来看continue
int main(void)
{
	for (int i = 1; i <= 10; ++i)
	{
		if (i == 5)
			continue;
		printf("%d ", i);
	}
	return 0;
}

在这里插入图片描述

  • 从运行结果可以看到,为什么这次在for循环中直接打印会输出1234,然后678910呢,而不是像while循环那样在4的地方终止,然后死循环,这就是因为这个i++是for循环每次进入的时候就会执行,就和我们在while循环中把i++放到上面来是一个道理
  • 对于这点,我们在下一个模块来详细讲一下==for的执行流程==

执行流程解释

  • 但是初次接触这个for循环的同学一定对这个循环的执行流程有所困惑,因为对于while循环的话,代码虽然是写得比较散,但是却很好理解,for循环就会显得堆在一起的感觉
  • 我们一起来看一下这个执行流程到底是怎样的

在这里插入图片描述

  • 从上图可以看出循环进入时,首先执行第一个表达式,也就是初始化部分,然后在进行条件判断,若是不为0,则继续执行下面的循环体语句,执行好后又来到表达式3【expr3】,调整循环变量后继续
  • 若是这个条件判断依旧【! = 0】,此时会继续进入循环,然后直到条件循环为0为止,才会结束循环
  • 然后可以看到图中还有这个【continue】和【break】,对于continue来说呢,就是跳出本次的循环,不执行下面的循环体语句了,直接回到循环体开头对循环变量进行调整;而对于break呢,可以看到,直接来到了这个【expr1 == 0】这条支路,说明会直接结束循环的运行

📕多学几招📕

接下去给大家讲讲在for循环使用时的一些注意事项和一些技巧

  • 你认为下面这段代码的执行结果是什么
int main(void)
{
	for (int i = 1; i <= 10; ++i)
	{
		printf("%d ", i);
		i = 5;		//循环体内不要随意改变循环变量
	}
	return 0;
}

在这里插入图片描述

  • 可以看到,【满屏的666】
  • 如果大家觉得我讲到好的话,在弹幕上扣一波【666】,不过CSDN的文章里没有这个功能,但是你可以在评论区扣(doge)
  • 好,言归正传,为什么这个直接结果会都是666,但是你自己观察可以发现,第一个打印的是1,这个时候你就可以根据我上面的for循环流程图,将这个

3、do…while循环

讲完了while、for,接下去让我们来看看do…while循环,这也是循环语句的一种,只是在各个应用场景中没有while和for来得广泛

语法介绍及案例分析

  • 首先来看一下它的语法格式,和while循环很类似
do
 循环语句;
while(表达式);
  • 然后再来说一下其特点,因为do…while循环在执行到时一定会进入循环体,然后再进行一个判断看看是否要继续执行。但是对于大多数使用循环的场景,都是需要先进行一个判断,然后在执行一个循环,所以这就让do…while的使用场景变得十分有限

我们先来一个具体的小案例

  • 业务逻辑是需要使用do…while循环去打印1~10的数字,这个逻辑的话我们在while循环和for循环都有实现过
  • 刚才说过,对于do…while循环,遍历到的时候会直接进入循环体,这里会打印i然后i++,判断i【i == 2】,所以继续执行循环,直到这个i加到11的时候便不再进入循环,结束程序的运行
//使用do while循环打印1~10
int main(void)
{
	int i = 1;
	do
	{
		printf("%d ", i);
		i++;
	} while (i <= 10);
	return 0;
}

do…while中的break和continue

对于break和continue这两个关键字,也是从本文的开始就一直陪伴我们,让我们到 do…while中来看看它们要如何使用

break

int i = 1;
do
{
	if (5 == i)
		break;
	printf("%d ", i);
	i++;
} while (i <= 10);

在这里插入图片描述

  • 可以看到,其实和while循环是相似的,也是当i为5的时候终止循环

continue

int i = 1;
do
{
	if (5 == i)
		//break;
		continue;	//死循环
	printf("%d ", i);
	i++;
} while (i <= 10);

在这里插入图片描述

  • 可以看到,对于continue也是一样,若是这个i++在continue语句的后面,则在continue语句执行到的时候,就会强制进入下一循环,那么这个i便一直会是5了,始终不会被打印出来,就造成了死循环

四、实战小训练

1、 计算 n的阶乘

首先第一题比较简单,计算一个n的阶乘,那只要定义一个变量去存放这个阶乘的结果,然后通过循环去遍历即可

  • 这里要注意,ret初始要置为1,否则在任何数和0相乘必定为0
int main(void)
{
	//n的阶乘
	int n = 0;
	scanf("%d", &n);
	int ret = 1;
	for (int i = 1; i <= n; ++i)
	{
		ret *= i;
	}
	printf("ret = %d\n", ret);
	return 0;
}

2、 计算 1!+2!+3!+……+10!

第二小题又是一道计算阶乘的题,不过这题计算的阶乘不是单个的,而是计算一个累加阶乘的和,我们一起来看看

  • 假设我们是要计算1! + 2! + 3!,数字小一些。首先来分析一下思路,因为要计算的数字只到3,所以我们需要一个外层循环从1遍历到3,表示求解的是这3个数的阶乘

  • 然后内层循环就是每一个数的阶乘求解,从1遍历到这个数即可,然后使用一个累乘变量存放起来。在计算完每一个数字的阶乘后,我们就需要去累加这些阶乘的和,所以还需要定义一个sum变量去存放这个和,最后sum变量就是我们所要求的结果

  • 正确答案应该是1 + 2 + 6 = 9,我们通过VS来跑一下看看

int ret = 1;
int sum = 0;
for (int i = 1; i <= 3; ++i)
{
	for (int j = 1; j <= i; ++j)
	{
		ret *= j;
	}
	sum += ret;
}
printf("sum = %d\n", sum);

在这里插入图片描述

  • 可以看到,sum的结果并不是9,多了。这是为什么呢?
  • 这里其实就是很多通过都会犯的错误,因为我们是在求解不同数字的阶乘,但是使用的累乘变量却是同一个ret,所以下一个数字需要计算的时候,这个ret还是上一次数字累乘后的结果,这就导致了阶乘计算的不准确,所以结果也会不同
  • 具体的错误大家可以自己去DeBug试一试,是这个3!在计算的时候多乘了一个2!遗留下来的2,所以使得3!= 12,多了6,因此最终结果也就多了6

在这里插入图片描述

  • 应该加上这句话才对,在每一次更新需要求阶乘的数时,都将这个累乘变量ret重置
  • 其实还有一种更加方便的方法,使用一次循环就可以把结果计算出来,具体的演示过程已经给出,大家自己分析一下即可
int ret = 1;
int sum = 0;
for (int i = 1; i <= 3; ++i)
{
	ret *= i;
	sum += ret;
	//i		ret		sum
	//1		 1		 1
	//2		 2		 3
	//3		 6		 9
}

3、 在一个有序数组中查找具体的某个数字n【讲解二分查找】

然后就是这道在个有序数组中查找具体的某个数字n

  • 那有同学一看到要在一个数组中查找一个数字,就想到去遍历这个数组,这个逻辑是对的,但是不够巧妙,我们需要更加巧妙一点的办法去解决这个问题
  • 这里介绍一个查找方法,叫做==二分查找==,什么叫二分查找呢?就是在一个数组的左右两段各定义一个指针,然后取它们的中间值,然后将你要查找的数字与这个中间值进行比较,若是比中间值要小,那么就去除掉后面那一部分区间,只需要找前面就可以了,若是比中间值要大,则去除掉前面那一部分区间,找后面。将这个逻辑放到一个循环中,不断地去更新中间值,然后做比较,最后左右两段的指针会重合,表示这个区间快要结束了,若是当左指针大于右指针时,也就意味着已经查找到所需要的元素或者是没有找到,此时便要退出循环进行一个相对的打印说明
  • 讲完了这一块的逻辑后,我们来看一下具体的代码
int main(void)
{
	//在一个有序数组中查找具体的某个数组n
	//1 2 3 4 5 6 7 8 9 10

	//二分法
	int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(a) / sizeof(a[0]);
	int key = 7;
	int left = 0;
	int right = sz - 1;
	
	while (left <= right)
	{
		int mid = (left + right) / 2;		//中间元素下标一定在循环内部,不断变化
		if (key < a[mid])
			right = mid - 1;
		else if (key > a[mid])
			left = mid + 1;
		else
		{
			printf("找到了,下标是%d\n", mid);
			break;		//找到了便跳出循环
		}


	}
	if (left > right)
		printf("没找到此元素\n");
	return 0;
}

4、编写代码,演示多个字符从两端移动,向中间汇聚【微信手机端看不到视频】

然后我们来玩一个很有趣的东西,将多个字符从两端移动,向中间汇聚

  • 我们来看一下,演示多个字符从两端移动向中间汇聚,我们需要实现一个覆盖的效果,比如我下面定义了两个数组,我们最终需要打印出来的效果是第一个数组的内容,但是在打印过程中我们需要打印第一个数组,然后也是通过两段左右指针去进行一个覆盖的效果
  • 首先是对于左右指针的初始化,左指针就是0,但是对于右指针的话,这里取不到它的末尾值,所以这个时候我们需要使用到strlen()这个库函数去求解数组1的长度,然后-1就是右指针的位置
  • 我们也是将这个逻辑放到一个循环中,然后把第一个数组的内容通过两个左右指针赋给第二个数组,然后这两个指针不断往中间移动,直至左指针大于右指针为止
  • 这里为了让其慢慢地显示出来,呈现一个动画的效果,又使用到了一个库函数,也就是Sleep()睡眠函数,这个函数还是蛮有意思的,参数的话你需要传入毫秒值,比如说1s执行一个就传入1000,0.5执行一下就传入500。还有这个函数需要引入头文件【#include <Windows.h>】,大家记得加上哦
int main(void)
{
	char arr1[] = { "welcome to bit!!!" };
	char arr2[] = { "*****************" };

	int left = 0;
	int right = strlen(arr1) - 1;
	while (left <= right)
	{
		arr2[left] = arr1[left];
		arr2[right] = arr1[right];
		printf("%s\n", arr2);
		Sleep(500);

		left++;
		right--;
	}
	return 0;
}
  • 然后我们通过运行结果来看看这个效果是怎样

[video(video-JUqwkuMS-1666671841689)(type-csdn)(url-https://live.csdn.net/v/embed/247980)(image-https://video-community.csdnimg.cn/vod-84deb4/eb50040f2835448ba6dad09d0382ed32/snapshots/ca1a0d18a916465fb39ff11274cfe2e3-00001.jpg?auth_key=4820212508-0-0-ce3c6bccf52d912ef9a3f83b65b96cfc)(title-QQ录屏20221024174502)]

为了让其在一行打印,不需要一行行地打印,我们可以使用这样一个命令

system("cls");	//清屏,一行打印
  • 但是这个system()函数要加上头文件【#include <stdlib.h>】,大家不要忘了。我们加上这条语句后再来看看运行效果是怎样的

[video(video-Q24mZ0io-1666671888836)(type-csdn)(url-https://live.csdn.net/v/embed/247982)(image-https://video-community.csdnimg.cn/vod-84deb4/34e4bd79021942348ed09b3b0de4a343/snapshots/7c8bb45bbd40414c9649054fe16d1990-00001.jpg?auth_key=4820217046-0-0-2b87b46aaedb5f32dd4773bfb9c60369)(title-两端移动)]

5、密码校验

这道题的话就是我们输入一串字符,然后通过与正确密码进行一个比较,看看是否相同,若是相同,则直接break跳出循环,若是不同则继续输入,但是机会只有3次,用完之后便跳出循环不允许再输入了

  • 实现逻辑并不难,你可以先自己写写看,检验一下自己学得怎么样,然后再来和我的程序进行一个比较
int main(void)
{
	int i = 0;
	char password[20] = { 0 };

	for (i = 0; i < 3; ++i)
	{
		printf("请输入密码>:");
		scanf("%s", password);
		if (strcmp(password, "bitbit") == 0)
		{
			printf("密码输入正确\n");
			break;
		}
		else
		{
			printf("密码输入错误\n");
		}
	}
	if (i == 3)
	{
		printf("输入机会用完,请30分钟后再试\n");
	}

	return 0;
}
  • 这里要注意的一个地方是有挂字符串比较的问题,我们不可以直接使用【==】去进行一个比较,而是要通过一个字符串的库函数strcmp进行比较。有关其返回值的问题,我们通过Cplusplus来看看

在这里插入图片描述

  • 也就是当这个第一个字符串的首字母大于第二个字符串的首字母时,就返回大于0的数字,小于0则是相反,若是两个字符串相同,则返回0。这里比较的是ASCLL码值的大小

6、猜数字游戏【经典】

好,接下来我们来玩一个C语言中很经典的游戏,叫做==猜数字==

  • 首先我们来实现一个整体的逻辑,也就是当你input输入的时候,通过我们上面学习的switch语句进行一个分支的判断,是要继续猜数字还是退出游戏
  • 一起先来看一下代码,首先你要有一个菜单供玩家去选择
void menu()
{
	printf("********************************\n");
	printf("**********  1.play  ************\n");
	printf("**********  0.exit  ************\n");
}
int main(void)
{
	int input = 0;
	do 
	{
		menu();
		printf("请输入你的选择:>");
		scanf("%d", &input);
		switch (input)
		{
			case 1:
				printf("猜数字\n");
				break;
			case 0:
				printf("退出游戏\n");
				break;
			default:
				printf("输入错误,重新输入\n");
				break;
		}
	} while (input);
	return 0;
}

在这里插入图片描述

  • 可以看到,整体的逻辑已经实现了让你按下【1】的时候,会实现一个猜数字的功能,若是一直按1,则可以一直猜数字,直到你按下0为止就可以
  • 接下我们就要实现猜数字的内部游戏逻辑,这个逻辑其实也是非常简单,通过系统随机生成一个数字,然后你在屏幕上去输入这个数字去猜,若是比这个数大,则提示数字大了;若是比这个数小,则提示数字小了,然后直到你刚好猜中为止

首先我们来讲讲这个随机数是如何生成的

  • 这就要用到C语言中的一个函数叫做rand(),你可以在Cplusplus中找找看这个,可以看到,这是一个生成随机数的函数,参数是空参。它的范围是【0~32767】

在这里插入图片描述

  • 但是我们要实现的是随机生成一个1~100之间的数字,这个时候你就需要在这个函数后面对100进行取余即可,就是【0-99】之间的数字,再在后面加上一个1,就是【1-100】
	//1.生成随机数
	int ret = rand() % 100 + 1;		//1 ~ 100
  • 我们通过printf来打印一下,看看系统生成了那些随机数
  • 可以看到,在一次游戏的过程中,数字是不一样的,但是在第二次又打开时,确实和第一次相等的随机数,这就出现了问题,你要想若是玩家碰到了这样的情况,每次都是相同的数字,它还会继续玩下去吗,当然这只是打个比方:camera:

在这里插入图片描述

  • 那我们要怎么去解决这个问题呢?这个时候我们就要使用到一个C语言中的另一个函数叫做随机种子srand(),我们通过Cplusplus再来看看

在这里插入图片描述

  • 但是呢,光有这个函数还不够,一般还要再配合使用一个时间戳函数time()

在这里插入图片描述

  • 直接让你看看具体是如何使用的
srand((unsigned int)time(NULL));
  • 我们来分析一下,首先看到这个time()函数,里面的参数设置了NULL为空,然后前面为什么要强转成(unsigned int)呢,因为这个srand()函数的参数类型就是(unsigned int),所以需要进行一个强转
  • 这句话大家要记得加在游戏循环的外面,因为这个随机种子最好是不要多次使用,用一次就可以了,多次使用的话会导致一些错误,所以将其放在main函数开头的位置即可
  • 还有一点不要忘了,这个time()函数是要包含头文件的【#include <time.h>】
  • 我们加上这句话后再来看看两次打开游戏的运行结果是怎么样的

在这里插入图片描述

  • 可以看到,产生了这个随机的效果
  • 好,后面我们就要来真正地实现这个猜数字的逻辑了,直接让你看看代码就会了:wind_chime:
  • 对了,记得把这个随机生成的打印数字去掉,不然你就看到答案了
int num = 0;
while (1)
{ 
	printf("请猜:>");
	scanf("%d", &num);
	if (num < ret) {
		printf("小了\n");
	}
	else if (num > ret) {
		printf("大了\n");
	}
	else {
		printf("猜对了\n");
		break;
	}
}
  • 我们来看看运行结果。可以看到,整体的整个简易逻辑已经得到了实现,你可以自己到编译器中试试看:computer:

在这里插入图片描述

五、goto语句【了解即可】

接下去我们来说说goto语句,对于goto语句,开头有提到过,是属于转向语句的一种,其余的还有break、continue和return语句上面在讲分支和循环语句的时候都已经有涉及到了

  • 我们直接来看一个小案例,这里是在一个输出语句printf的后面加了一个goto语句,然后执行这个goto语句的话会跳到输出语句的前面, 然后继续执行下面的代码,这里要注意,【跳转的地方变量后面是要使用冒号的!!!
  • 然后我们来看一下运行结果
flag:	//这里是冒号!!!
	printf("hehe\n");
	goto flag;

在这里插入图片描述

  • 可以看到,上面是输出了很多的【hehe】,表明这已经进入了一个死循环了,当然这只是一个小案例,让你了解一下goto语句的执行流程,平常写代码可不能这么去写

  • 然后我们再来看一个,下面这段代码的意思是在打印完一个【hehe】语句的时候,执行一个goto语句,然后会直接跳转到下一个【heihei】语句,并不会执行【haha】语句
  • 真的会是这样吗,让我们一起来看一下结果
	printf("hehe\n");
	goto flag;
	printf("haha\n");		//不会被打印
flag:	//跳到这里
	printf("heihei\n");

在这里插入图片描述

  • 可以看到,【haha】语句并没有被执行,这也印证了我们的猜想
  • 以上就是goto语句的一些基本用法就,大家了解一下即可,不用去过度深究,因为对于goto语句的话其实在某些地方使用是不太好的,因为程序是一行行执行的,若是进行了一个跳转的话就会导致整个程序的逻辑混乱,所以还是不太建议大家去使用这个语句
  • 一般的goto语句真正适合运用的地方其实是==多层嵌套循环的地方==,可以节省break子句的频繁使用,只需要一次goto即可跳出循环。

虽然goto语句不为广为使用,但是上面说过了,在某些场合下还是可以派的上用场的,下面我们来说一个使用goto语句的经典案例

  • 下面这段程序很有趣,是网上的一个段子改编的,使用system()函数调用一个60s后关机程序,然后输入【***】,若是和它要你输入的内容一直,就取消关机程序,若是不一致,则使用goto语句继续跳转回来,直到你输入的内容是它要的内容为止
  • 哈哈,看了这个输入内容确实是很有趣,大家下去可以自己试试看,是不是真的会关机【记得保存包文件哦doge】
	char input[20] = { 0 };
	system("shutdown -s -t 60");
again:
	printf("请注意,你的电脑在1分钟内关机,如果输入:我是猪,就取消关机\n");
	scanf("%s", input);
	if (0 == strcmp(input, "我是猪"))
	{
		system("shutdown -a");
		printf("取消关机\n");
	}
	else
	{
		goto again;
	}
  • 不过这段逻辑还可以使用while循环实现,然后符合规则的话就break跳出即可
char input[20] = { 0 };
system("shutdown -s -t 60");

while (1)
{
	printf("请注意,你的电脑在1分钟内关机,如果输入:我是猪,就取消关机\n");
	scanf("%s", input);
	if (0 == strcmp(input, "我是猪"))
	{
		system("shutdown -a");
		printf("取消关机\n");
		break;
	}
}

六、总结与提炼

  • 在本文中,我们学习了分支和循环语句,在分支语句中,我们学到了【if…else】和【if…elseif】分支判断,以及switch语句中case和default子句的使用。在循环语句中,我们学到了while循环、for循环、以及do…while循环,并且观察了break和continue子句在上述分支和循环子句中的具体使用和注意事项
  • 后期阶段,我们还通过了5个实战小训练,对这些知识点有了一个很好的回顾和消化,让大家对分支和循环语句的了解更加深刻
  • 在最后,我们又介绍了一个跳转语句goto,不过除了在多循环嵌套的情况下其他地方不建议使用

以上就是本文所要讲的所有内容,感谢您的观看,如有疑问请于评论区留言或私信我:rose:

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。