写点什么

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

作者:Fire_Shield
  • 2023-04-14
    浙江
  • 本文字数:14600 字

    阅读完需:约 48 分钟

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 语句的书写也是有讲究的,我们一起来看一下


  • 你认为下面两段代码那个是书写规范一些的


//代码1if (condition) {    return x;}return y;
复制代码


//代码2if(condition){    return x;}else{ return y;}
复制代码


  • 答案是第二段,其实它们所要表达的意思都是一样的,这个 condition 就是条件。若是条件成立,则 return x,若是不成立,则 return y

  • 但是为什么说第二段代码更好地,并不是它写得更长一些,而是这个逻辑框架比较清晰,让阅读者看了一目了然


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


//代码3int num = 1;if(num == 5){    printf("hehe\n");}
复制代码


//代码4int 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 之间的奇数


//way1int i = 1;while (i <= 100) {  if (i % 2 == 1)    printf("%d\n", i);  i++;}
复制代码


//way2int 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;}
复制代码


<font color = "#ff0000">注意事项:break 千万别忘了,很重要,不然会穿透的</font>

📰强大的面试题📰

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


  • 你认为下面会输出多少呢❓


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】,接下去我们来总结一下


<font color = "#ff0000">continue - >>某种条件成立时跳过后面的代码</font><font color = "#ff0000">break - >>某种条件成立时整个循环不执行</font>

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~10int 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:

发布于: 刚刚阅读数: 4
用户头像

Fire_Shield

关注

语言观决定世界观 2022-09-02 加入

高校学生,热爱编程,喜欢写作

评论

发布
暂无评论
C生万物 | 分支和循环语句【内含众多经典案例】_C语言_Fire_Shield_InfoQ写作社区