分支与循环 - L

分支与循环 - L

分支与循环

这章节我们会讲到以下几个知识:

  1. 分支语句

    • if
    • switch
  2. 循环语句

    • while循环
      - for循环
      - do...while循环
  3. goto语句

还是那句话,我只是一个刚入门的小菜鸟,哪有有讲的不好,还望大家海涵。

先讲一个知识点,在C语言中什么是语句呢?C语言中由一个分号;隔开的就是一条语句。比如:

#include <stdio.h>
int main ()
{
    1+2;//这是一条语句
    printf("Hello
");//这也是一条语句
    ;//这也是一条语句
    return 0;
}

在C语言中所有语句都分为三大类型:

  1. 顺序结构
  2. 选择结构
  3. 循环结构

顺序结构很简单,程序从上至下地执行,中间无任何判断和跳转。流程示意图:

下面的我就没继续画了,大家懂就可以了。而下面重点要讲的是分支与循环结构。

分支语句(选择语句)

分支语句也称选择语句,什么叫选择呢?就好比在大学里,如果好好学习,校招时就拿一个好offer。如果不好好学习,那就回家种田吧。而学不学呢就是一种选择。

我画的图描述的清楚吗?各位客官?分支语句也分为以下几种:

  • if语句
  • switch语句

if语句

if语句,if是啥意思?如果的意思。那相对应的是不是还有else否则的意思?那我们看看if语句在C语言中是怎样描述的呢?

语法形式
if(表达式)
    语句;
if(表达式)
    语句1;
else
    语句2;
//多分支
if(表达式)
    语句1;
else if(表达式)
    语句2;
//
....
else
    语句3;

解释:就是如果表达式结构为真就执行语句1反之执行语句2,if(表达式)表达式为真就执行if后面的语句,如果为假就执行else后的语句。那有人就问了在C语言中怎样描述真假的呢?

在C语言中 0代表假,非0代表真。

举个例子吧,如果if(1>2),这个表达式的结果是什么?1会大于2吗?显然是不会的所以这个表达式的结果为假也就是0。

用代码形式来讲吧:

#include <stdio.h>
int main()
{
    int  age = 9;
    if(age<18)
    {
        printf("未成年
");
    }
    return 0;
}
//代码2
int main()
{
    int age = 18;
    if(age>=18)
    {
        printf("成年人
");
    }
    else
    {
        printf("未成年
");
    }
    return 0;
}
//代码3
int main()
{
    int age = 25;
    if(age<18)
    {
        printf("未成年
");
    }
    else if(age>=18&&age<30)
    {
        printf("青年");
    }
    else if(age>=30&&age<55)
    {
        printf("中年
");
    }
    else
    {
        printf("老年人");
    }
    return 0;
}

看看效果吧:

image-20211214160305104

效果果然跟预想的是一样的,有人有点好奇为啥要加大括号呢?我们看看下面:

image-20211214160434074

我们看到报错了,为啥呢?那为啥下面没错呢?

image-20211214160501686

if语句默认只能控制一条语句所以程序报错。因为在C语言中,if和else不加括号默认控制一条语句,我们可以试试:

image-20211214160820896

看到虽然没有报错,但是它多打印了一条语句,再换一个:

image-20211214160911228

可以知道确实if语句和else语句默认只能控制一条语句。我们可以加上大括号来防止这些错误。

接着我们看代码3的运行效果:

image-20211212174402046

年龄大家可以自行修改,或者加入一条输入语句,自己输入想要判断的年龄,代码3是属于if语句中的多分支语句,也就是有多重选择,如果怎么怎么样会怎么怎么样,又如果怎么怎么样又会怎么怎么样,..........最后否则怎么样。这是属于if语句的知识点。

我们检测一下:

#include <stdio.h>
int main()
{
    int a = 0;
    int b = 2;
    if(a==1)
        if(b==2)
            printf("hello
");
    else
        printf("heihei
");
    return 0;
}

想想结果是啥?是hello还是heihei呢?咱们看看运行效果:

image-20211214160947677

啥也没输出,为什么呢?这涉及到一个知识点:

if语句中if会与最近的else进行配对

所以这题具体的意思就是:

#include <stdio.h>
int main()
{
    int a = 0;
    int b = 2;
    if(a==1)
        if(b==2)
            printf("hello
");
        else
        	printf("heihei
");
    return 0;
}
//也等于
if(a==1)
{
    if(b==2)
    {
        printf("hello
");
    }
    else
    {
        printf("heihei
");
    }
}
//或者想打印heihei这样改造
int main()
{
    int a = 0;
    int b = 2;
    if(a==1)
    {  
        if(b==2)
        {
            printf("hello
");
        }
    }
    else
    {
        printf("heihei
");
    }	
    return 0;
}

第一个判断是否成立呢?0会等于1嘛?肯定不会的,也没有想匹配的else语句所以啥也不会输出。所以大家要注意哦,书写规范还是挺重要的,当然知识点也不能忘掉。我们再看两段代码:

#include <stdio.h>
int main()
{
    int n = 5;
    if(n==7)
    {
        printf("hello");
    }
    return 0;
}
//代码2
int main()
{
    int n = 5;
    if(7==n)
    {
        printf("hello");
    }
    return 0;
}

这两段代码各位觉得如何?个人觉得代码2要更好,更严谨,如果我们代码1的形式少了个=号,会怎么样?

image-20211214161204751

确实是正常运行的,但是n的值被改变了,这可不是我们 想要的结果呀?我们换下:

image-20211214161103080

如果我们换一种写法,就直接报错了,所以这种写法还是严谨的。逻辑更加清晰,不容易出错。

练习:

  1. 判断一个数是否为奇数
  2. 输出1~100之间的奇数
#include <stdio.h>
int main()
{
    int n = 5;
    if(n%2!=)
    {
        printf("奇数
");
    }
    else
    {
        printf("偶数
");
    }
    return 0;
}
//2
int main()
{
   int i = 1;
    while(i<=100)
    {
        if(i%2!=0)
        {
            printf("%d ",i);
        }
        i++;
    }
}

Switch语句

我们再讲分支语句中另外一大分支语句 -- Switch语句。Switch语句用于多分支的情况。比如说输入1~7中的任意数字,然后输出相对应的日期。

输入1,输出星期一

输入2,输出星期二

.....

输入7,输出星期日

虽然我们使用if..else if ...else if....else的形式可以完成要求,但是太复杂了,代码会过于冗余也就是太长了,这个时候我们就可以用Switch语句。

switch (整型表达式)
{
    case 整型常量:
        语句;
        break;
    case 整型常量2:
        语句;
        break;
        //.....
    default:
        语句;
        break;
}

我们用上面的例子:

#include <stdio.h>
int main()
{
    int day = 0;
    scanf("%d",&day);
    switch(day)
    {
        case 1:
            printf("星期一
");
            break;
        case 2:
            printf("星期二
");
            break;
        case 3:
			printf("星期三
");
            break;
        case 4:
            printf("星期四
");
            break;
        case 5:
            printf("星期五
");
            break;
        case 6:
            printf("星期六
");
            break;
        case 7:
            printf("星期日
");
            break;
        default:
            printf("输入错误
");
            break;
    }
    return 0;
}

效果如下:

image-20211214161639543

这里用英语的形式是因为鄙人正在学习英语哈,勿怪勿怪。

看了效果后,带大家理解理解这玩意,Switch(整型表达式)这里只能是整型或者字符型,因为字符型其实也是整型的一种嘛,然后case后面也必须跟的是整型常量值,这是规定,别问我我也不大了解。至于每条语句为啥加了break呢?break是什么意思呢?break在英语中是打破、弄坏、停止运转的意思,那么在计算机中又是啥意思呢?在初始C语言中,我们也知道这个break是关键字,在C语言中break是指跳出,结束的意思。就是跳出程序的意思,我们试着把这个去掉看看:

image-20211214161726304

咋一看全打印出来了,为啥呢?就是因为少了跳出的关键字,我们加上一个试试:

image-20211214161751630

我们加上一条break语句后,确实后面的没有打印了,可是我们输入的是1呀,应该只会输出一个呀?因为只有一个break语句,所以我们为了应对要求所以应该在每条case语句后加上break,这样就可得到之前的效果。注意看我还输入一条语句就是default语句,这个又是啥意思呢?我们看效果:

image-20211214161827498

输出的竟然是输入错误,也确实,我们生活中也没有星期八的存在呀,default是什么意思呢?默认、违约的意思,同样的在C语言中,default也是默认的意思,就是除了我们给出的几个常量值,如果我输入的值不在这几个之内,那么我们就可以用这个语句提醒用户输入是错误的。这是一个好的编程习惯,这样我们可以总结出几个编程习惯:

  • 在case 语句的后面加上一条 break语句。
  • 按要求来给每个case语句加break语句。
  • 在每个 switch 语句中都放一条default子句是个好习惯,甚至可以在后边再加一个 break 。

break语句可以把语句列表划分成不同的部分。比如1 ~ 5都是工作日,6 ~ 7是休息日。

#include <stdio.h>
int main()
{
    int day = 0;
    scanf("%d",&day);
    switch(day)
    {
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
            printf("weekday
");
            break;
        case 6:
        case 7:
            printf("playday
");
            break;
   		default:
            break;
    }
    return 0;
}

我们看看效果:

image-20211212205124391

因为break把1 ~ 5分为一部分,无论是输入1还是其他1~5之间的任何数输出都是weekday,同样的把6 ~7也分为一部分。

下面我们来做一道Switch嵌套Switch的练习:

#include <stdio.h>
int main()
{
	int n = 1;
	int m = 2;
	switch(n)
	{
        case 1:
            m++;
        case 2:
            n++;
        case 3:
				switch(n)
                {
                    case 1:
                        n++;
                    case 2:
                        m++;
                        n++;
                        break;
                }
        case 4:
            m++;
            break;
        default:
            break;
	}
    printf("m = %d, n = %d",m,n);
    return 0;
}

大家思考一下是多少呢?

image-20211212195753579

大家看到m=5,n=3,为什么呢?解析:

int n = 1;
int m = 2;
switch(1)
{
case 1://因为n是1所以执行case1
  m++;//m=m+1 = 2+1 = 3
case 2:
  n++;//n=2
case 3://因为上局case语句没有break语句所以会继续执行下面语句
  switch(2)//n=2
  {
      case 1:
          n++;
      case 2://因为n=2所以执行的是这条语句
          m++;//m=3+1=4
          n++;//n=2+1=3
          break;//遇到跳出语句了所以跳出这个Switch语句
  }
case 4:
  m++;//接着上面的数据 m=4+1=5  n = 3
  break;//遇到跳出语句所以跳出整个Switch语句后面的代码不再执行
}

循环语句

循环语句,也就是重复的做某件事情,比如说我需要打印100行“Hello World”,虽然说可以用100条printf函数打印出来,但是也需要太长的代码了,所以我们采用循环语句来做。咱们看看流程图:

image-20211212205812551

在循环语句中又分为3中类型:

  1. while
  2. for
  3. do...while

while循环

C语言中while循环的语法规定:

//while语法结构
while(表达式)
{
    循环语句;//循环体
}
int i = 0;//初始化
while(i<10)//条件判断
{
    i++;//循环变量调整
}

我们用流程图来说明while循环:

画的不是很好大家谅解谅解。这大概就是while循环的流程图了。我们来看看实例:

打印1~100的数字

#include <stdio.h>
int main()
{
    int i = 1;
    while(i<=100)
    {
        printf("%d ", i);
        i = i+1;
    }
    return 0;
}

效果如下:

image-20211212211347246

循环条件如果恒为真那就死循环了。在while循环也是可以使用break语句跳出循环的,比如说我遇到了20就退出循环:

image-20211212212218568

可以看到确实到了20就已经退出了循环,那我只想不要20这个数字,继续打印后面的呢?请看下面:

image-20211213084415965

可以看到确实跳过了20直接打印后面的数字了。可是数字1也没有打印出来,原因是i的值在进入循环后就发生了自增,所以直接跳过了数字1,如果想得到1也很简单,我们把i的值初始化为0即可,通过观察break和continue语句的作用,我们得出结论:

在循环中只要遇到break,就停止后期的所有的循环,直接终止循环 。while中的break是用于永久终止循环的 。

continue是用于终止本次循环的,也就是本次循环中continue后边的代码不会再执行,而是直接跳转到while语句的判断部分。进行下一次循环的入口判断。

注意:

while循环嵌套continue语句时注意循环变量的增值表达式的位置,也就是上面的i++;

我们把循环变量的增值表达式放到下面来看看:

image-20211214102815334

20虽然没有打印,但是后面的21也没有打印是因为,循环变量的增值表达式放在后面进行的,一旦i等于20,就直接跳过本次循环了,后面的语句就不会执行,所以i==20不会被改变的。注意这一点

咱们看几道练习题:

#include <stdio.h>
int main()
{
    int c = 0;
    while((c=getchar())!=EOF)
    {
        putchar(c);
    }
    return 0;
}
//2
int main()
{
    int c=0;
    while((c=getchar())!=EOF)
    {
        if(c<"0"||c>"9")
        {
            continue;
        }
        putchar(c);
    }
}

大家看到这两段代码是不是很陌生?getchar是是啥?getchar是获取字符的意思,也可理解为输入字符的意思,跟输入语句有点类似,那与之相对应的输出字符就是putchar,那EOF又是啥呢?EOF是end of file的缩写,也就是文件结束标志,跟字符串的‘’有点相似。我们看它是怎么定义的:

image-20211213091254309

使用define定义的,实际上是-1。那整段代码是什么意思呢?getchar读取失败的时候会返回EOF,如果读取字符不等于EOF就进入循环,然后打印字符。

image-20211213091737911

可以看见确实输出了我们输入的字符,那我们要怎样才能结束循环能?Ctrl+Z就可以结束了。那getchar是怎样读取字符的呢?是直接从键盘上读取吗?其实并不是,在getchar和键盘之间有一个缓冲区,程序刚开始,缓冲区是空的,需要输入数据到缓冲区,比如我们输入字符A然后回车,其实回车也算字符,回车也就是 ,所以会把字符A和 一起放进缓冲区,然后getchar去读取字符,读取字符A放到变量c,然后在屏幕上打印字符A,这时缓冲区只剩下 ,然后getchar继续读取,读取到 就换行了,然后此时缓冲区就空了,需要继续输入字符,这也是后面光标在闪的原因。


那代码2又是什么意思呢?我们知道了代码1是输入字符然后再打印字符,那中间加了一个判断语句,很显然就是只会打印‘0’~"9"

image-20211214101717707

判断条件是如果c小于字符0或大于字符9就跳过本次循环,因为有continue关键字嘛,所以只有输入字符0到字符9之间的任意数都可以在屏幕上打印相对应的字符。这是这两题的解析,那我们知道这种代码有什么用呢?我们看个实例:

#include <stdio.h>
int main()
{
    char password[10]={0};
    int c = 0;
    printf("请输入密码:");
    scanf("%s",password);
    printf("请确认密码(Y/N):
");
    c = getchar();
    if(c == "Y")
    {
        printf("确认成功
");
    }
    else
    {
        printf("确认失败
");
    }
    return 0;
}

运行结果:

image-20211214103451540

我还没开始输入确认与否呢,它直接来一句确认失败。这不玩儿呢?还记得我们之前画的那张图嘛?解析:

程序开始我们输入:123456+回车键,这就相当于在缓冲区放了123456+ ,我们输入语句scanf会把我们输入的字符串给拿走放到password里去,但是 还在缓冲区,此时getchar就会去缓冲区里读取 所以后面判断条件是假的。

那我们该咋办呢?

思路:既然缓冲区还有 那我们再用一个getchar来读取这个字符

image-20211214104217518

emmmm确实是可以,但是如果我们如果输入其他字符串呢?比如说中间有空格键之类的:

image-20211214104329331

这是为啥呢?不就是加了个空格吗?这跟scanf函数有关了,scanf函数碰到空格就不会继续去字符了,这样缓冲区就还剩下空格加 123然后getchar读取一个字符空格,后面再判断,判断条件也会为假,所以程序结果如图上所示。使用了几次scanf语句,我来解释一下scanf的用法:

int i = 0;
scanf("%d",&i);//%d以十进制的形式输入   &i是取i的地址 &是scanf中必须要使用的 上面代码没有使用是因为数组名是数组首元素的地址使用可以不加&符号。

那我们怎样解决呢?

思路:既然getchar语句是一个一个的读取,而且我们输入之后都会按回车键( ),那我们可以使用循环的方式来读取后面的字符,如果是 就跳出循环,然后再判断是否需要确认密码。我们把回车给干掉。这样也相当清理缓冲区。

image-20211214105354108

如果我们在做题的时候可能会碰到类似的题目,当我们碰到这种题的时候要记住还有个缓冲区的存在。

for循环

我们已经知道while循环了,在C语言中还有个常用的另外一个循环 ---- for循环。流程图如图所示:

画的不好大家见谅哈。

首先看for循环的语法:

语法:

for(表达式1;表达式2;表达式3)
{
	循环语句;
}
//表达式1为初始化部分,用于初始化循环变量的。
//表达式2为条件判断部分,用于判断循环时候终止。 
//表达式3为调整部分,用于循环条件的调整。

用法:

#include <stdio.h>
int main()
{
    int i = 0;
    //  初始化  条件判断  循环变量调整
    for(i = 1; i < 11; i++)
    {
        printf("%d ",i);
    }
    return 0;
}

这段代码会打印出什么呢?

初始化:i = 1,条件判断:i < 11为真进入循环 循环语句: 打印1 循环变量调整: i++

i = 2 i < 11 打印2

.....

i = 10 i < 11为真 打印10 i++

i = 11 i < 11为假 不进入循环

经过分析代码,想必大家也知道这段代码会打印什么了吧。没错就是1~10。在for循环中也可以使用breakcontinue语句。作用都是一样的。但是continue有点不一样:

image-20211214112222769

它不会像while循环那样会出现死循环,因为它调整部分是在外边进行的,跳过里面的语句返回到上面的调整部分。

continue在for循环中跳过本次循环,后面的语句不执行,直接跳到调整部分。

for循环的循环控制变量

1.不可在for循环体内修改循环变量,防止for循环失控,造成死循环。

2.建议for语句循环控制变量的取值采用“前闭后开区间”写法。

试下第一条:

image-20211214112933544

这样就造成了死循环,所以我们在使用for循环是千万不要再循环体内改变循环变量的值。

第二点是什么意思呢?我们可以使用一个数组来说明一下:

#include <stdio.h>
int main()
{
    int arr[10]={0};
    int i = 0;
    for(i=0;i<10;i++)//这就是前闭后开的写法 当然也可以写成for(i=0;i<=9;i++)
    {
        printf("%d ",arr[i]);
    }
    return 0;
}

前闭后开,大家应该有听说过这个数学中有学过前闭后开就是前面等于后面小于或大于的写法,比如上面的前闭后开在数学的写法就是[0,10),大于等于0小于10。虽然也可以写作前闭后闭区间,但是写成这样的形式更直观,一眼就能看出是循环多少次。

看下面代码结果是什么呢?

#include <stdio.h>
//1
int main()
{
    for(;;)
    {
        printf("hello
");
    }
    return 0;
}
//2.
int main()
{
    int i,j;
    for(i=0,j=0;i<3&&j<5;++i,j++)//++i或者i++都可以不影响
    {
        printf("hello
");
    }
    return 0;
}

代码1

image-20211214135907938

  • 结果是死循环打印hello
  • 虽然说for语句中初始化、条件判断、调整3部分都能省略,但是判断部分省略会导致程序死循环,因为判断部分省略会导致判断部分恒为真。
  • 除非特殊要求,否则不要省略。

再换个代码把上面改一下:

#include <stdio.h>
int main()
{
    int i = 0;
    int j = 0;
    for(;i<3;i++)
    {
        for(;j<3;j++)
        {
            printf("hello
");
        }
    }
    return 0;
}

那这样结果是什么呢?

image-20211214135755884

为什么?

i = 0; i < 3为真 : j = 0; j < 3为真;打印hello;j++ j = 1 ; j < 3为真;打印hello; j++; j = 2;j<3为真 打印hello; j++; j = 3 ;j < 3为假 不进入循环;i++;

i = 1;i < 3为真;j = 3;j < 3为假 不进入循环。i++

....

除非要求需要,不然还是不要轻易省略for语句的三部分的任意部分。

代码2:

image-20211214140646790

  • 结果只打印3次hello
  • i = 0;j = 0; i小于3并且j小于5,条件为真进入循环打印hello
  • i = 1;j = 1;条件依然为真 打印hello
  • i = 2;j = 2;条件依然为真 打印hello
  • i = 3;j = 3;条件为假,不打印 跳出循环。
  • 初始化、判断、调整这三部分可以是一条语句也可以是多条语句。

看一道习题:

//请问循环几次?
#include <stdio.h>
int main()
{
    int i = 0;
    int j = 0;
    for(i=0,j=0;j=0;i++,j++)
        	j++;
    return 0;
}

循环几次呢?其实一眼就能看出,循环0次,为什么呢?因为压根儿就不会进入循环。条件判断部分是赋值,把0赋给j,然后j是0,0为假所以不会进入循环。

do...while循环

前两个循环是不是都要先判断才能进入循环,这个循环可就不一样了,它允许先执行然后再判断是否还需执行,先执行再判断。流程图:

语法

do
{
    循环语句
}while(表达式);//判断

特点

循环至少执行一次,使用场景有限制,所以很少看见do...while的使用。

使用

打印1~10:

#include <stdio.h>
int main()
{
    int i = 1;
    do
    {
        printf("%d ",i);
        i++;
    }while(i<11);
    return 0;
}

image-20211214142539031

  • 该循环无论如何一上来就会打印i,然后再接着判断是否小于11,是的话就会接着执行循环。

再试试break和continue在do...while循环中是怎么样的:

#include <stdio.h>
int main()
{
	int i = 1;
    do
    {
        if(5 == i)
            break;
  	  printf("%d
", i);
      i++;
    }while(i<=10);
	return 0;
}
//2
int main()
{
    int i = 1;
	do
	{
        if(5 == i)
        	continue;
		printf("%d
", i);
        i++;
	}while(i<=10);
    return 0;
}

代码1:

image-20211214143900637

正常跳出循环。

代码2:

image-20211214144006453

死循环了。跟while循环有点类似。

break:跳出break所在的循环体,终止循环。

continue:跳过本次循环continue后面的语句,直接进入下一次循环。

练习题

  1. 计算 n的阶乘。
  2. 计算 1!+2!+3!+……+10!
  3. 在一个有序数组中查找具体的某个数字n。 编写int binsearch(int x, int arr[], int n); 功能:在arr[0]
    <=arr[1]<=arr[2]<= ….<=arr[n-1]的数组中查找x.
  4. 编写代码,演示多个字符从两端移动,向中间汇聚。
  5. 编写代码实现,模拟用户登录情景,并且只能登录三次。(只允许输入三次密码,如果密码正确则提示登录成,如果三次均输入错误,则退出程序 。

计算n的阶乘

阶乘怎么算?阶乘就是 n * n-1 * n-2 * n-3*...*1,那不就是1乘到n嘛,代码如下:

#include <stdio.h>
int main()
{
	int i = 0;
	int n = 0;
	scanf("%d", &n);
	int  t = 1;
	for (i = 1; i <= n; i++)
	{
		t *= i;
	}
	printf("%d ", t);
	return 0;
}

运行结果:

image-20211214145734531

思路:

阶乘就是1 * 2 * ....* n。那么我们用循环,弄出1~n的数字,然后再定义一个变量t=1,然后用t去成 1 ~ n的每一个数字,从而得到最后的结果。

计算1!+....+10!

计算1!+~10!,我们之间有计算了n的阶乘,那我们在计算n的阶乘之上再嵌套一层循环是不是就可以解决了?

#include <stdio.h>
int main()
{
    int i = 0;
    int t = 1;
    int sum = 0;
    for(i=1;i<=10;i++)
    {
        int j = 0;
        for(j=1;j<=i;j++)
        {
            t *= j;
        }
        sum+=t;
    }
    printf("%d",sum);
    return 0;
}

image-20211214150931722

思路:

要求1 ~ 10的阶乘和,那么算出每个数的阶乘然后再相加在一起就可以了,首先一层循环控制1 ~ 10的数字,这下数字出来了,然后再定义一个循环,循环的初始化为1,第二个循环用来计算阶乘,定义一个变量t,赋值为1,这样每次相乘都是1去乘j的值,不会出现像计算完2的阶乘后,用2的阶乘的值再去计算3的阶乘,这样会出错的,所以在循环内部定义变量t,这样阶乘后在相加在一起,就可以得到1的阶乘加到10的阶乘了。

这种求法效率比较低,我们可以优化一下它

4! = 4 * 3 * 2 * 1; = 4*3!

3!= 3 * 2 * 1;=3*2!

2!= 2 * 1;=2*1!

通过观察我们发现n!会等于n * n-1!。那我们可以用一个循环就能搞定:

#include <stdio.h>
int main()
{
    int i = 0;
    int ret = 1;
    int sum = 0;
    for(i=1;i<=10;i++)
    {
        ret *= i;//ret=1*1; ret=1 * 2;  ret = 2 * 3;  ret = 6 * 4;..........
        sum += ret;//sum=0+1;sum=1+2;sum=3+6;.....
    }
    printf("%d",sum);
    return 0;
}

image-20211214152634967

很明显这种方法效率高。

<span id = “3">查数

在有序数列中查找一个数字,这题挺简单,我们暴力求解就好了:

#include <stdio.h>
int main()
{
    int arr[]={1,2,3,4,5,6,7,8,9,10};
    int x = 6;
    int sz = sizeof(arr)/sizeof(arr[0]);
    int i = 0;
    for(i=0;i<sz;i++)
    {
        if(x==arr[i])
        {
            printf("找到了,下标是%d",i);
            break;
        }
    }
    if(sz==i)
    {
        printf("找不到
");
    }
    return 0;
}

image-20211214153352665

思路:

我们在一组数字中找一个数首先我们得知道这组数字有多少个,然后再一个一个的去比对,匹配成功就说明找到了,并且退出循环,如果一组数字全部比对完了都没有找到那就是找不到了。

其实还有个更优化的算法,既然是有序的一组数字,那么我先拿这个需要被查找的数字跟最中间的那个数字去做比对,这样就可以省掉一大半的数据去比对了,如果要查找的数字比中间那个数字要大,那左边的一半数字可以扔掉不要了,继续在后面的数字中寻找,然后再用这个方法去比对,最终只剩一个数字再比对,如果没有那就找不到了。这种方法被称为 二分查找法,写成代码又是怎么样的呢?

#include <stdio.h>
int main()
{
	int arr[]={1,2,3,4,5,6,7,8,9,10};
    int x = 6;
    int sz = sizeof(arr)/sizeof(arr[0]);
    int left = 0;
    int right = sz - 1;
    while(left<=right)//只有坐下标小于或者是等于右下标时候循环才能进行,不然找不了
    {
        int mid = (left+right)/2;
        if(x > arr[mid])
        {
            left = mid+1;
        }
        else if(x < arr[mid])
        {
            right = mid -1;
        }
        else
        {
            printf("找到了,下标为%d",mid);
            break;
        }
    }
    if(left > right)
    {
        printf("找不到
");
    }
	return 0;
}

我们看看实际效果:

image-20211214155926033

是不是就找到了呢?这种方式是不是效率更高了。其实大家也可以拿一张纸,在上面写上一组有序的数字,然后把纸折半去找,这样大家会有更深刻的理解。

<span id = “4">多个字符从两端向中间汇聚

首先我们要明白题目的含义,知道是什么意思。多个字符从两端向中间汇聚大概就是以下情况:

image-20211214162245613

这样的话要怎么做呢?我们可以定义两组数组,一组存#########另一组存放welcome to China!!!!,因为我们使用的是字符串,所以需要使用字符串函数求出字符串的长度。然后使用循环,一次把两端的字符放进##数组里去,然后打印,然后两端的下标一加一减,慢慢向中间靠近,就可以打印出这种效果了:

#include <stdio.h>
int main()
{
    char arr1[]="####################";
    char arr2[]="welcome to China!!!!";
    int left = 0;
    int right = strlen(arr2)-1;//strlen是求字符串长度 需要引头文件string.h
    while(left<=right)
    {
        arr1[left]=arr2[left];
        arr1[right]=arr2[right];
        printf("%s",arr1);
        Sleep(1000);//打印完后停留一会,让我们能更清楚的看是怎样变换出来的。需要引头文件Windows.h
        system("cls");//清空屏幕
        left++;
        right--;
    }
    printf("%s",arr1);
    //for循环版
    for(left=0,right=strlen(arr2)-1;left<=right;left++,right--)
    {
        arr1[left]=arr2[left];
        arr1[right]=arr2[right];
        printf("%s",arr1);
        Sleep(1000);//打印完后停留一会,让我们能更清楚的看是怎样变换出来的。需要引头文件Windows.h
        system("cls");//清空屏幕
    }
    return 0;
}

image-20211214163855545

我把所有的都打印出来更直观点。

模拟登录

只允许输入三次密码,如果密码正确则提示登录成功,如果三次均输入错误,则退出程序。

思路:

创建两个字符数组,用循环加判断结构

易错点:字符串相比较不能直接用==,而是要用字符串比较函数strcmp。strcmp(str1,str2),返回值为0就相等。

#include <stdio.h>
int main()
{
   	char password[]="admin";
    char str[10];
    int i = 0;
    for(i=0;i<3;i++)
    {
        printf("请输入密码:");
        scsanf("%s",str);
        if((strcmp(str,password))==0)
        {
            printf("密码正确,登录成功
");
            break;
        }
    	else
        {
            printf("密码错误,请重新输入,只有3次机会哦
");
        }
    }
    if(3==i)
    {
        printf("机会用完,退出程序
");
        exit(0);//exit是退出程序语句
    }
    return 0;
}

image-20211214165016205

提一下字符串比较函数是怎么比较的:

char str1[]=“abcde”;

char str2[]=“abcdf”

在使用strcmp在比较str1和str2的时候,从左到右依次比较每个字符,比较的是字符的ASCII码值,前4个都是相同的都相等,但是‘e’ 和‘f’就不一样,e的ASCII码值是101,f的ASCII码值是102,所以str1<str2。strcmp返回的就是小于0的值。

猜数字游戏

学了这么多知识了,我们来玩个游戏吧,猜数字。我们输入一个数字,程序会提醒我们输入的数字是大了还是小了,直到我们猜中这个数字。

游戏逻辑:

  1. 电脑自动生成1~200之间的数字。
  2. 猜数字
  • 猜对了,游戏结束。
  • 猜错了,继续猜直到猜对为止。

实现程序思路:

  1. 程序一开始首先打印一个简单的菜单,让用户好选择。
  2. 因为程序无论如何都会执行一次,所以用do....while循环,因为有菜单所以要有选择,用Switch来判断进入哪个环节
  3. 游戏开始后,要生成1~200之间的随机数
  4. 接着才游戏,然后判断猜的数字与随机生成的数字是否相等,可以用个死循环,猜中了就提示游戏结束,退出至菜单页面。

难点在生成1~200之间的随机数上。

产生随机数

生成随机数的函数是rand函数,头文件<stdlib.h>,rand函数返回值是整型。rand函数返回的是0~32767的数字,但是在调用rand函数之前需要使用srand函数设置随机数的生成器,就是在rand函数之前需要使用srand函数,srand函数设置一个随机的起点,需要的参数是个unsigned int无符号整型数字,而srand函数参数如果是一个定值的话,那随机数都是一样的,所以srand函数中要给个变得值,我们可以传一个可变化的量 ---时间 ,时间传进去的实际上是时间戳,怎么获取时间戳呢?用time函数,给time函数传个NULL空指针,time函数返回的是也是整型,而srand需要的是一个unsigned int所以强制类型转换成unsigned int类型。time函数需要头文件<time.h>

srand = ((unsigned int)time(NULL));,我们整个工程中只需要设置一次随机数就可以了。因为需要的是1~200的随机数,所以我们让rand生成的随机数%200,因为%200的余数是0 ~ 199,我们加上1,就得到了1~200的随机数了。

参考代码:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void menu()
{
	printf("******************************************
");
	printf("************    1. play    ***************
");
	printf("************    0. exit    ***************
");
	printf("******************************************
");
}
void game()
{
    int ret = rand()%200+1;
    int k = 0;
    while(1)
    {
        printf("请输入猜的数字:");
        scanf("%d",&k);
        if(k<ret)
        {
            printf("数字小了
");
        }
        else if(k > ret)
        {
            printf("数字大了
");
        }
        else
        {
            printf("恭喜你猜中了
");
            break;
        }
    }
}
int main()
{
    int input = 0;
    srand((unsigned int) time(NULL));
    do
    {
        menu();
        printf("请输入选择:");
        scanf("%d",&input);
        switch(input)
        {
            case 1:
                game();
                break;
            case 0:
                printf("退出游戏
");
                break;
            default:
                printf("输入错误
");
        }
    }while(input);
    return 0;
}

代码效果:

image-20211214184702781

goto语句

C语言中提供了可以随意滥用的 goto语句和标记跳转的标号。
从理论上 goto语句是没有必要的,实践中没有goto语句也可以很容易的写出代码。

但是某些场合下goto语句还是用得着的,最常见的用法就是终止程序在某些深度嵌套的结构的处理过程,例如一次跳出两层或多层循环。

这种情况使用break是达不到目的的。它只能从最内层循环退出到上一层的循环。写个程序举个例子:

#include <stdio.h>
int main()
{
flag:
    printf("hello
");
    printf("world
");
    goto flag;
    return 0;
}

执行顺序是:首先进入主函数,然后两端输出函数,flag不用它,走到goto 语句时,它会跳转到flag的位置开始执行,然后接着执行到goto语句,就造成了死循环。

再比如写个关机程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
    //关机命令 60秒后自动关机
    system("shutdown -s -t 60");//system()执行系统命令函数  引用头文件<stdlib.h>
     char arr[20]={0};
again:
    printf("请注意,你的电脑将1分钟内关机,如果输入:我是小猪猪,就关机");
    scanf("%s",arr);
    if((strcmp(arr,"我是小猪猪"))==0)
    {
        system("shutdown -a");
    }
    else
    {
        goto again;
    }
    return 0;
}

image-20211214190709075

image-20211214190735013

其实我们用循环也可以实现这个功能的:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
    //关机命令 60秒后自动关机
    system("shutdown -s -t 60");//system()执行系统命令函数  引用头文件<stdlib.h>
     char arr[20]={0};
	while(1)
    {
    	printf("请注意,你的电脑将1分钟内关机,如果输入:我是小猪猪,就关机");
   	 	scanf("%s",arr);
        if((strcmp(arr,"我是小猪猪"))==0)
        {
            system("shutdown -a");
            break;
        }
    }

    return 0;
}

实际上goto语句使用的场景是:

for(...)
    for(...)
    {
        for(...)
        {
            if(disaster)
            goto error;
  		}
	}
	…
error:
	if(disaster)

其实在我们自己写程序的时候很少会用到goto语句的,大家知道这个语句就可以了,要是真的要使用的话,查一下资料也会的。

数字炸弹游戏

我自己写了一个数字炸弹游戏不知大家有木有玩过呀?其实就是猜数字和关机程序的结合,学完之后自己也突发奇想之前有玩过这种游戏所以尝试一下自己写这个游戏,参考代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void menu()
{
	printf("******************************************
");
	printf("***********欢迎来到数字炸弹游戏!!*********
");
	printf("************    1. play    ***************
");
	printf("************    0. exit    ***************
");
	printf("******************************************
");
}
void game()
{
    int ret = rand() % 200 + 1;
    int guess = 0;
    char str[20] = { 0 };
    int i = 0;
    for (i = 0; i < 5; i++)
    {
        printf("请输入猜的数字:
");
        scanf("%d", &guess);
        if (guess > ret)
        {
            printf("数字大了
");
        }
        else if (guess < ret)
        {
            printf("数字小了
");
        }
        else
        {
            printf("恭喜你猜中了!
");
        }
    }
    if (5 == i)
    {
        printf("你输了!作为惩罚你的电脑将在1分钟后关机!!!
");
        system("shutdown -s -t 60");
    }
    printf("如果你想继续玩耍,就输入“我是小猪猪”。只有3次机会哦!
");
    for (i = 0; i < 3; i++)
    {
        printf("请输入:
");
        scanf("%s", str);
        if ((strcmp(str, "我是小猪猪")) == 0)
        {
            system("shutdown -a");
            break;
        }
    }
    if (3 == i)
    {
        printf("机会用完了哦,1分钟后关机
");
        system("shutdown -s -t 60");
        exit(0);
    }

}
int main()
{
    int input = 0;
    srand((unsigned int)time(NULL));
    do
    {
        menu();
        printf("请输入选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            game();
            break;
        case 0:
            printf("退出游戏
");
            break;
        default:
            printf("输入错误
");
        }
    } while (input);
    return 0;
}

有哪些知识点没讲好的大家见谅哈,菜鸟报道哈哈哈哈。大家一起加油呀!!!谢谢大家的收看!!!阿里嘎多。

荆轲刺秦王img