文章目录
前言
C语言是结构化的程序设计语言!它分为:顺序结构、选择结构、循环结构。一段代码正常运行的话就是顺序结构,那么什么是选择结构和循环结构呢?这就是我们今天要讲的。
一、什么是语句?
二、分支语句(选择结构)
1.if语句
那if语句的语法结构是怎么样的呢?
语法结构:
if(表达式)
语句;
if(表达式)
语句1;
else
语句2;
if(表达式1)
语句1;
else if(表达式2)
语句2;
else
语句3;
#include <stdio.h>
//代码1
int main()
{
int age = 0;
scanf("%d", &age);
if(age<18)//如果输入的数据小于18就输出未成年!
{
printf("未成年\n");
}
return 0;
}
//代码2
int main()
{
int age = 0;
scanf("%d", &age);
if(age<18)//如果输入的数据小于18就输出未成年!
{
printf("未成年\n");
}
else//如果输入的数据等于或大于18就输出成年-这里省略了else的表达式age>=18(一般都是省略的)
{
printf("成年\n");
}
return 0;
}
//代码3
int main()
{
//其实&&前面的条件可以省略的,但是为了代码更好理解,所以就加上
int age = 0;
scanf("%d", &age);
if(age<18)
{
printf("少年\n");
}
else if(age>=18 && age<30)
{
printf("青年\n");
}
else if(age>=30 && age<50)
{
printf("中年\n");
}
else if(age>=50 && age<80)
{
printf("老年\n");
}
else
{
printf("老寿星\n");
}
return 0;
}
if语句的大括号其实还是可以省略的哦(但是只能控制一条语句,如果要输出多条还是要带着大括号哦)
int main()
{
//但是当if没带大括号只能控制一条语句
if (age >= 18)
printf("成年\n");
//所以如果要输出多条语句要带着大括号,即代码块
else
{
printf("未成年\n");
printf("不能谈恋爱\n");
}
return 0;
}
所以我们一般写if语句的时候都会带上大括号,无论有多少条语句。这可是一个好习惯,会避免很多错误的呢。
1.1 悬空else
如果你因为if的大括号可以省略,写了下面的代码:
int main()
{
int a = 0;
int b = 2;
if (a==1)
if (b == 2)
printf("hehe\n");
else
printf("haha\n");
return 0;
}
那么恭喜你,踩到坑啦,这段代码根本输出不了什么东西哦。有同学就会问了,这是为什么呢,它们都只输出一条语句呀,为什么不能省略呢?
其实是因为这里的else悬空了。这是为什么呢?因为else是跟最近的if匹配的,这里的a=0,明显不符合if的条件,那么它就是其他情况,那它就要寻找else进入,可是这个时候的else却和离它最近的if配对了,所以a=0找不到else来匹配,那么程序自然就不会输出东西啦!正是因为这个代码写的不够明了,导致判断错误,这里也就突出大括号的重要性了。
所以在这里我们要修改一下代码,让它可以正常运行。
int main()
{
int a = 0;
int b = 2;
if (a == 1)
{
if (b == 2)
{
printf("hehe\n");
}
}
else
{
printf("haha\n");
}
return 0;
}
这个时候就能成功输出啦,所以我们要懂的代码分隔的重要性,写if语句带上大括号,是一个很好的写代码习惯哦!
1.2 if书写形式的对比
我们来看代码:
//代码1
if (condition) {
return x;
}
return y;
//代码2
if(condition)
{
return x;
}
else
{
return y;
}
//代码3
int num = 1;
if(num == 5)
{
printf("hehe\n");
}
//代码4
int num = 1;
if(5 == num)
{
printf("hehe\n");
}
看起来这些代码都差不多,但是其实代码2和代码4更好,逻辑更加清晰,不容易出错。这是怎么看出来的?我们来对比一下,首先是代码1和代码2:他们大括号的位置是不一样的哦,代码1有可能会导致我们在使用大括号的时候搞混,所以我们正常的写法是代码2那种,这样一一对应就能确保代码的正确性。
那么久有人要问了,代码3和代码4是有什么区别吗?这是因为代码3的if条件如果不小心只输入了一个等号就会使程序bug,num=5这个条件也是可以正常使用的,但是已经脱离我们原本打算的条件。但是如果我们使用代码4,将常量放在左边的话,那么上面这种情况就会被避免,因为5=num这种条件是不被允许的,会报错,这样子就可以让我们及时检查到错误哦。
所以你知道为什么代码2和代码4更好了吗!
1.3 小小练习巩固一下
1、判断一个数是否为奇数
int main()
{
int num = 15;
if (num % 2 == 1)
printf("奇数\n");
return 0;
}
2、输出1-100之间的奇数
int main()
{
int i = 0;
for (i = 1; i <= 100; i++)
{
if (i % 2 == 1)
printf("%d,", i);
}
return 0;
}
2.switch语句
switch ( 整型表达式 ){语句项;}
而语句项是什么呢?
// 是一些 case 语句:// 如下:case 整形常量表达式 :语句 ;例如:switch(good){case 1:printf("厉害");}
2.1 在switch语句中的 break
int main()
{
int day = 1;
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;
}
那么有人就会问这个代码是怎么个运行法呢,其实case决定入口,break决定出口,switch后面的整型表达式的值是多少,那么它就决定了进入哪个case。像上面的代码,因为day=1,那么我们就要找到那个case后面的整形常量表达式为1的那个入口,进入到case里面输出星期一,遇到break退出整个程序的运行。
int main()
{
int day = 0;
scanf("%d", &day);
switch (day)
{
case 1:
case 2:
case 3:
case 4:
case 5:
printf("工作日\n");
break;
case 6:
case 7:
printf("休息日\n");
break;
}
return 0;
}
2.2 default子句
int main()
{
int day = 0;
scanf("%d", &day);
switch (day)
{
case 1:
case 2:
case 3:
case 4:
case 5:
printf("工作日\n");
break;
case 6:
case 7:
printf("休息日\n");
break;
default:
printf("输入错误\n");
break;
}
return 0;
}
如果上面day输入的数据超过1-7这个范围的话,我们的代码就会走到default这里,它会为我们输出一句:“输入错误”,避免我们输入超过1-7的数据代码不会显示任何东西的情况,让调试代码的人可以知道自己输入错误。
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;
}
三、循环语句(循环结构)
循环语句有下面这几种:
whilefordo while
1.while循环
我们已经掌握了if语句:
if ( 条件 )语句 ;
//while 语法结构while ( 表达式 )循环语句 ;
比如我们要实现:在屏幕上打印1-10的数字
int main()
{
int i = 1;
while (i <= 10)
{
printf("%d ", i);
i++;
}
return 0;
}
每次进入while循环体里面的时候都会将i打印出来并且让i++,直到i>10的时候while循环才结束。当i=10输出后,i++变成了11,不符合while的循环条件,这个时候就会停止循环啦。
1.1 while语句中的break和continue
break介绍
int main()
{
while (1)
printf("hehe\n");
return 0;
}
这段代码会输出什么呢?猜对了,因为while的循环条件永远为真(非0数),所以这段代码会无限打印hehe,那我们再来看看下面这段代码:
int main()
{
while (1)
{
printf("hehe\n");
break;
}
return 0;
}
这段代码只会打印一次hehe,这是为什么呢?这是因为有break的存在,当我们的循环遇到break就会立刻跳出循环,结束程序
continue介绍
int main()
{
int i = 1;
while (i <= 10)
{
if (5 == i)
continue;
printf("%d ",i);
i++;
}
}
这段代码会出现什么情况呢?那我们要先了解一下continue在代码中的作用:
在while循环中,continue的作用是跳过本次循环continue后面的代码,跳转到while语句的判断部分,进行下一次循环判断。
所以由此我们可以知道上面的代码会出现什么情况啦,当i=1时,符合while的循环条件,所以进入循环,输出i=1,然后i++。直到i++等于5的时候,此时走到if条件的时候,i=5刚好符合,那么进入if语句,遇到continue,代码重新跳回while进行判断,此时i还是等于5,依旧符合循环的条件,我们再次进入循环,我们又走到if,i又符合等于5的条件,再次进入if,遇到continue,重新跳回去...因为continue的存在,代码一直在循环,i一直等于5。所以上面代码运行的结果是:
一直在循环,结果也根本显示不出来,因为i++在continue后面所以导致i一直是5,循环也一直走不下去。
那么我们要怎么样修改才能正确使用continue呢?
int main()
{
int i = 1;
while (i <= 10)
{i++;
if (5 == i)
continue;
printf("%d ",i);
}
}
我们只需将i++放到前面,那么我们这个代码就不会进入死循环啦,continue跳出来,我们i++就会将i变成6,不会一直在5死循环哦,所以我们在使用continue的时候一定要注意,不要写成死循环代码啦。
代码1:int main()
{
int ch = getchar();
putchar(ch);
return 0;
}
int main()
{
int ch = 0;
while ((ch = getchar()) != EOF)
{
putchar(ch);
}
return 0;
}
EOF(end of file)是文件结束标志并且EOF=-1。由于getchar获取的是ASCII码,而ASCII代码值的范围是0~127,不可能出现-1,因此可以用EOF作为文件结束标志,无论你输入什么都是不会等于EOF的,所以这个代码可以一直循环下去,打印出你输入的东西。
当你按下Ctrl+z的时候就能将这个程序停止哦!
//代码2
int main()
{
char ch = '\0';
while ((ch = getchar()) != EOF)
{
if (ch < '0' || ch > '9')
continue;
putchar(ch);
}
return 0;
}
1.2 getchar和while的配合使用
假设如果我们要模拟实现一个设置密码的程序,我们应该怎么做呢?这个时候我们试着用getchar来写一下这个代码:
int main()
{
char password[20] = { 0 };
printf("请输入密码:>");
scanf("%s", password);
printf("请确认密码(Y/N):>");
int ch = getchar();
if (ch == 'Y')
{
printf("确认成功\n");
}
else
{
printf("确认失败\n");
}
return 0;
}
这个时候我们运行,发现出现了问题,我们输入完密码一敲回车就自动弹出了确认失败:
这是为什么呢?
其实在这段代码中我们先设置一个数组负责接收密码,再利用scanf完成对密码的读取。但是由于scanf的输入需要敲回车,可scanf仅仅读取数字,并不会读取\n,所以导致\n被遗留下来。这就导致了getchar直接读取\n,自动输出下面写入的内容(因为getchar获取的不是Y而是其他情况,所以程序就显示确认失败了)。
这是因为引入getchar的东西都是存在缓冲区的,所以需要清理缓冲区,确保getchar读取的是我们输入的东西,而不是读取scanf遗留在缓冲区的东西,那么我们的目的就是要把\n给去除掉,避免getchar读到我们不想要的东西。
这个时候我们要怎么办呢,我们尝试先在代码中再一个getchar来获取\n符号,看看效果
int main()
{
char password[20] = { 0 };
printf("请输入密码:>");
scanf("%s", password);
printf("请确认密码(Y/N):>");
getchar();//处理\n
int ch = getchar();
if (ch == 'Y')
{
printf("确认成功\n");
}
else
{
printf("确认失败\n");
}
return 0;
}
当我们输入123456 abc的时候又出现这种情况,这究竟是为什么呢?
所以ch读取了bc\n被判定为其他,输出确认失败,那我们要怎么修改才能让代码正常使用呢。
我们需要设置一个可以清理缓冲区多个字符的循环体,这样子我们就能确保将缓冲区里的所有东西都清理掉,包括\n,也会被清理掉
int main()
{
char password[20] = { 0 };
printf("请输入密码:>");
scanf("%s", password);
printf("请确认密码(Y/N):>");
//清理缓冲区中的多个字符
int tmp = 0;
while ((tmp = getchar()) != '\n')
{
;
}
int ch = getchar();
if (ch == 'Y')
{
printf("确认成功\n");
}
else
{
printf("确认失败\n");
}
return 0;
}
2.for循环
2.1 语法
for(表达式1; 表达式2; 表达式3)
循环语句 ;
int main()
{
int i = 1;//初始化
while (i <= 10)//判断部分
{
printf("%d ", i);
i++;//调整部分
}
return 0;
}
而对于for来说:
表达式1:对应while的初始化部分
表达式2:对应while的判断部分
表达式3:对应while的调整部分
int main()
{
int i = 0;
//for(i=1/*初始化*/; i<=10/*判断部分*/; i++/*调整部分*/)
for (i = 1; i <= 10; i++)
{
printf("%d ", i);
}
return 0;
}
for语句执行的流程:
int i = 0 ;// 实现相同的功能,使用 whilei = 1 ; // 初始化部分while ( i <= 10 ) // 判断部分{printf ( "hehe\n" );i = i + 1 ; // 调整部分}// 实现相同的功能,使用 forfor ( i = 1 ; i <= 10 ; i ++ ){printf ( "hehe\n" );}
2.2 for语句中的break和continue
break介绍
int main()
{
int i = 0;
for (i = 1; i <= 10; i++)
{
if (i == 5)
break;
printf("%d ", i);
}
return 0;
}
运行后的结果:
当满足if条件的时候就break跳出整个程序。
continue介绍
int main()
{
int i = 0;
for (i = 1; i <= 10; i++)
{
if (i == 5)
continue;
printf("%d ", i);
}
return 0;
}
运行后的结果:
当满足if条件后重新运行程序,跳过continue下面的代码,所以这里运行后的结果并没有5的出现。
2.3 for语句的循环控制变量
for循环语句的建议:
1、不要在for循环体内修改循环变量,防止for失去控制
2、建议for语句的循环控制变量的取值采用“前闭后开区间”的写法(i=0,i<10)
int i = 0 ;// 前闭后开的写法for ( i = 0 ; i < 10 ; i ++ )//这里i的取值范围是[1, 10) - 前闭后开{}// 两边都是闭区间for ( i = 0 ; i <= 9 ; i ++ )//这里i的取值范围是[1, 9] - 前后都是闭{}
2.4 一些for循环的变种
//代码1
int main()
{
for (;;)
{
printf("hehe\n");
}
return 0;
}
//代码2
int main()
{
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
printf("hehe\n");
}
}
return 0;
}
大家猜猜会打印多少次hehe呢?答案是9次。这是为什么,因为当i和j进入循环体后,首先i是符合循环的,那我们就进入第一个for里面,进入后j也符合循环,也进入循环,这个时候打印出hehe,j++变成1,依旧符合,继续打印. . .每一次i符合进入循环就会打印3次hehe,我们的i在这个程序进入了3次,所以我们最终会打印9次hehe。
那如果我们省略掉初始化部分,会打印多少次hehe呢?
//代码3
int main()
{
int i = 0;
int j = 0;
for (; i < 3; i++)
{
for (; j < 3; j++)
{
printf("hehe\n");
}
}
return 0;
}
运行结果:
有人就会疑惑了,这是为什么呀?
原来,每次i符合进入循环的时候,j都会被初始化一次,可是这次省略了初始化,导致j在经历第一次循环后一直都是3,不符合循环的条件,所以即使i符合循环进入了循环里面,j也不会再打印啦。
2.5 小小练习巩固一下
int main()
{
int i = 0;
int k = 0;
for (i = 0, k = 0; k = 0; i++, k++)
k++;
return 0;
}
大家觉得会循环多少次呢?
答案是:一次都不循环。因为k的判断条件被赋值为0,恒为假,所以这个循环条件是假的,那么假的当然不会执行啦!
3.do...while()循环
3.1 do语句的语法:
do循环语句 ;while ( 表达式 );
3.2 执行流程
3.3 do语句的特点
int main()
{
int i = 10;
do
{
printf("%d\n", i);
}while(i<10);
return 0;
}
像这种情况,i=10并不满足while里面的条件表达式,但是因为do while的特点,我们会先运行一次再根据while条件表达式判断是否进行下一次的循环。
3.4 do while循环中的break和continue
break介绍
int main()
{
int i = 1;
do
{
if (i == 5)
break;
printf("%d ", i);
i++;
} while (i <= 10);
return 0;
}
continue介绍
int main()
{
int i = 1;
do
{
if (i == 5)
continue;
printf("%d ", i);
i++;
} while (i <= 10);
return 0;
}
由于i++是在continue,那么根据我们上面所学的,这个代码应该会在输出1、2、3、4后死循环,我们来看看:
四、关于循环的练习题
1. 计算n的阶乘
这里就有同学要问了,什么是阶乘呢?1的阶乘就是1,2的阶乘就是1*2,3的阶乘就是1*2*3,n的阶乘就是1*2*3*4*5*...n。所以要完成阶乘的话就需要用到我们的循环语句,我们来看看怎么写这个代码:
int main()
{
int i = 0;
int n = 0;
int ret = 1;
scanf("%d", &n);
for (i = 1; i <= n; i++)
{
ret *= i;
}
printf("%d\n", ret);
return 0;
}
我们利用scanf输入n的次数,n=2则为1*2,n=3则为1*2*3以此类推。设置ret参数,负责接收我们相乘后的数据。利用for循环确定从i=1开始相乘,i<=n时保持循环,每次循环后i++,ret等于阶乘后的数。运行结果如下:
2. 计算 1!+2!+3!+……+10!
1!就是1的阶乘:1
2!就是2的阶乘:1*2
n!就是n的阶乘:1*2*3*...n
那么这个题其实要求的就是1到10的阶乘相加,我们可以根据上面的代码进行修改来解决这道题:
int main()
{
int i = 0;
int n = 0;
int ret = 0;
int sum = 0;
for (n = 1; n <= 10; n++)//我们让n最大到10,刚刚好得出我们这道题想要的答案
{
ret = 1;//每循环一次就对ret进行初始化
for (i = 1; i <= n; i++)
{
ret *= i;//ret存放每次阶乘的数
}
sum += ret;//sum存放1到10阶乘的和
}
return 0;
}
我们利用for循环来计算每一个n的阶乘,并将算出来的阶乘存到ret这个参数里面。然后在计算n的阶乘之前,把ret初始为1,这样子就能将每次计算出来的ret进行相加放到sum里面,且能保证ret不会在之前的数据上累加。
其实还有一种更加简单的方法,我们一起来看看:
int main()
{
int n = 0;
int ret = 1;
int sum = 0;
//第二种方法:简易,高效
for (n = 1; n <= 10; n++)
{
ret *= n;
sum += ret;
}
printf("%d\n", sum);
return 0;
}
同学们能知道这是为什么吗?其实1!=1,2!=1*2,3!=1*2*3,那么我们可不可以理解为,2!=1!*2,3!=2!*3,4!=3!*4...所以我们可以得出n!=(n-1)!*n。上面第二种方法就是利用了这个规律,当n为1时,ret=1!,当n为2时,ret*=2即为1!*2=2!,然后sum每一次+=得出的ret,最后当n=10的时候,不就能计算出1到10阶乘的和吗?
答案是一模一样的:
所以我们推荐使用第二种方法,这种方法更加锻炼我们的思维,能让我们找出其中的规律,将代码写得更好。
3. 在一个有序数组中查找具体的某个数字n
这道题我们要怎么做呢?在这里我们不得不提到一个方法,就是折半查找(二分查找)。那这个东西具体是怎么实现的呢,那么我来给大家描述一下:
想必同学们应该能够通过图片理解到我们折半查找(二分查找)是如何实现的了,那么现在我们一起来通过代码实现一下:
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//假设有一组数组
int k = 7;//这是我们要查找的数字
//在arr这个有序数组中查找k(7)的值的位置
int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
int left = 0;
int right = sz - 1;//因为数组是从0开始的,所以末尾的位置为元素个数-1
while (left <= right)//当left<=right的时候循环可以继续。
{//一旦left>right的时候就停止循环
int mid = (left + right)/2;//中间数 - 用于折半查询
if (arr[mid] < k)//如果中间数小于k值,那说明中间数左边包括中间数都小于k
{
left = mid + 1;//那么起始位置就要移到mid的后一位数,再重新确定mid值
}
else if (arr[mid] > k)//如果中间数大于k值,那说明中间数右边包括中间数都大于k
{
right = mid - 1;//那么结尾位置就要移动到mid前一位数,再重新确定mid值
}
else
{
printf("找到了,下标是:%d\n", mid);
break;//找到了就结束循环
}
}
if (left > right)//找不到的情况
{
printf("找不到了\n");
}
return 0;
}
有上面板书的解释,相信这段代码大家也都能写出来。我个人觉得如果自己能列出来要实现的想法,那么其实代码写起来还是很简单的。所以大家可以先把想法写出来,一条一条对应去实现,这样子能让你的脑子更清晰,代码也能写得更快。
4. 编写代码,演示多个字符从两端移动,向中间汇聚
将####################从两端移动,变成welcome to study!!!!那么我们要怎么处理这个问题呢?首先我们需要两个数组,分别存放####################和welcome to study!!!!然后两个数组分别进行替换,起始位置++,结束位置--,利用循环再一次替换。我们来实现一下:
#include <string.h>//使用strlen需要引用的头文件
#include <windows.h>//使用Sleep需要引用的头文件
int main()
{
char arr1[] = "welcome to study!!!!";//首先声明一个数组存储我们要打印的句子
char arr2[] = "####################";//再声明一个数组存放井号便于替换
//因为这个题是从两边替换过来,所以首先要定义两边下标的存放位置
left = 0;//存放数组最左边的元素下标
right = strlen(arr1) - 1;//存放数组最右边的元素下标
(数组长度-1就是最右边的元素下标)
while (left <= right)//当left小于等于right的时候,元素还是有的,要继续打印
{//当left大于right的时候,说明两边已经相交错开,没有元素了,所以交错时就不再循环
arr2[left] = arr1[left];//井号替换左边的元素
arr2[right] = arr1[right];//井号替换右边的元素
printf("%s\n", arr2);
Sleep(1000);//睡眠1秒,一步一步显示变化(Sleep的单位是毫秒)
system("cls");//清空屏幕,只显示一行,不显示全部变化
left++;//左边后移一个元素进入二次循环,进行下一次替换
right--;//右边前移一个元素进入二次循环,进行下一次替换
}
printf("%s\n", arr2);
//因为清空屏幕会导致最后没有显示结果,所以在外面还要显示一次最终的结果
return 0;
}
上面代码的实现其实就是利用循环,然后将两端进行替换。同时我们还使用了Sleep和system两个函数,想知道具体会出现什么效果的同学自己可以在编译器上运行一下哦,会有想不到的效果。
5. 编写代码实现,模拟用户登录情景,并且只能登录三次(只允许输入三次密码,如果密码正确则 提示登录成,如果三次均输入错误,则退出程序)
如何能够实现这道题呢?我们先假装密码是123456,然后我们可以使用scanf输入密码,并且将我们输入的密码放进参数里面,再将参数跟密码对比,如果对不上就通过循环重新输入新的密码进行对比,直到对比了三次还是错误的话,这个时候就退出循环,退出程序。以上就是我们对这道题的思路,我们尝试来实现一下:
#include <string.h>//使用strcmp需要引用的头文件
#include <windows.h>
int main()
{
int i = 0;//计数器,用于控制循环
char password[20] = { 0 };//初始化数组,用于存放输入的密码
for (i=0; i<3; i++)
{
printf("请输入密码:>");
scanf("%s", password);//password是数组,不需要取地址,直接使用也是读取首地址
if (strcmp(password, "123456") == 0)
//当strcmp比较后一样的时候会输出0,使用当strcmp==0时密码匹配上了
{
printf("登录成功!\n");
break;//匹配成功后跳出循环
}
else
{
printf("密码错误,请重新输入哦!\n");
}
Sleep(2000);//睡眠2秒
system("cls");//清空屏幕
}
if (i == 3)
{
printf("三次密码均输入错误,退出程序\n");
}
return 0;
}
有同学就要问了,为什么要用strcmp进行比较呢,为什么不可以使用if (password == "123456")呢?其实这种方法是错误的,因为这是两个字符串进行比较,两个字符串比较,不能使用==,应该使用strcmp。如果是两个数字进行对比,确实可以使用上面这个方法。同学们要记住哦,字符串比较是用strcmp函数。
五、折半查找算法
int bin_search(int arr[], int left, int right, int key)
{
int mid = 0;
while(left <= right)
{
mid = (left + right) / 2;
if(arr[mid] > key)
{
right = mid-1;
}
else if(arr[mid] < key)
{
left = mid+1;
}
else
{
return mid;//找到了,返回下标
}
}
return -1;//找不到
}
里面的arr就负责接收我们要查找的数组,left就接收数组的起始位置,right就负责接收数组的结束位置,key接收我们要查找的值。
六、猜数字游戏实现
1、自动产生一个1-100之间的随机数
2、猜数字
a、猜对了,就恭喜你,游戏结束
b、你猜错了,会告诉猜大了,还是猜小了,继续猜,直到猜对
3、游戏会一直玩,除非退出游戏
那么我们来思考一下,要实现这个程序首先我们需要什么呢?首先需要产生一个随机数,然后这个程序还是一个循环体,一直循环,直到猜对才退出循环。而且我们需要有一个菜单,在成功猜对后选择是否再次进行游戏还是退出程序,所以这里我们需要使用到do...while和switch语句,而且需要两个函数,一个放游戏的运行,一个放菜单。当我们思考到这里已经大差不差啦,那我们来实现一下:
我们先写一个菜单函数
void menu()
{
printf("*******************************\n");
printf("********** 1.play **********\n");
printf("********** 0.exit **********\n");
printf("*******************************\n");
}
然后我们再写一个实现游戏运行的函数:
void game()
{
int ret = 10;//假设这个数字是10
//猜数字
int guess = 0;
while(1)//因为猜数字不确定几次能猜对,所以我们这里保证循环会一直进行
{
printf("请猜数字:>");
scanf("%d", &guess);
if (guess < ret)
{
printf("猜小了\n");
}
else if (guess > ret)
{
printf("猜大了\n");
}
else
{
printf("恭喜,猜对了\n");
break;//因为程序是一直循环的,所以需要在猜对的时候break才会退出循环
}
}
}
这个时候我们就要来写主函数啦:
int main()
{
int input = 0;
do
{
menu();//打印菜单
printf("请选择");
scanf("%d", &input);
switch(input)
{
case 1:
game();//在上面定义game函数,运行整个游戏
break;//当你完成猜数字之后重新进入循环,再次选择要继续猜还是退出游戏
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
}while(input)
//当你input输入的是0时,即表示为假,则停止循环,刚好对应上面的选择0可以退出
//但是当你打印的为非0数时,表示为真,则继续循环,
//对应上了选择1开始游戏和对应上了选择错误重新循环选择
return 0;
}
我们将这几段代码拼起来运行:
发现可以正常运行,很完美,但是我们这里是固定了ret,并不是随机值呀,那每次猜的数都是一样的,有什么意思吗?我们现在就来将代码进行改造,让它产生真正的随机数。
在这里我们就要介绍rand函数了:这是一个生成随机数的函数,可以随机返回一个0-32767之间的数字。但是rand函数不可以单独使用的哦,它需要一个随机数生成器,才能真正实现随机。这个时候就需要srand的出现啦,srand是一个在调用rand之前设置的随机数生成器,和rand配套使用实现真正的随机。srand里面需要一个会改变的变量,才能成为真正调用它。那么什么是一直在改变的变量呢,其实就是计算机的时间戳,所以我们将时间戳当成它的参数,提供变化,让它实现随机,又由于srand需要一个unsigned int,所以强制转换时间戳的类型。time就是时间戳函数,所以在这里我们要定义一个srand((unsigned int)time(NULL))。代码经过我们修改后将会变成什么样呢?
#include <stdlib.h>//rand、srand函数需要引用的头文件
#include <time.h>//time函数需要引用的头文件
void menu()
{
printf("*******************************\n");
printf("********** 1.play **********\n");
printf("********** 0.exit **********\n");
printf("*******************************\n");
}
void game()
{
//猜数字游戏的实现
//1.生成随机数(rand函数)
//rand函数返回了一个0-32767之间的数字
//时间 - 时间戳
int ret = rand() % 100 + 1;//%100的余数是0-99,然后+1,范围就是1-100啦
//2.猜数字
int guess = 0;
while (1)
{
printf("请猜数字:>");
scanf("%d", &guess);
if (guess < ret)
{
printf("猜小了\n");
}
else if (guess > ret)
{
printf("猜大了\n");
}
else
{
printf("恭喜,猜对了\n");
break;
}
}
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
//因为不需要每玩一次就生成一次时间戳进行随机,所以我们只在整个程序里面定义一次就够了
//srand函数要放在rand函数调用之前的位置
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
那么当你成功写出这个代码的时候,就真正实现猜数字游戏啦,这也是我们学习以来写的一个比较长的代码,大家还是可以消化一下的。
七、goto语句
int main()
{
flag:
printf("hehe\n");
printf("haha\n");
goto flag;
return 0;
}
这个代码是怎么个运行过程呢?首先代码进入主函数里面,它不会去看flag里面的东西,直接走到goto这里,然后通过goto去读取flag里面的东西。那么这个代码运行结果是怎么样呢?其实是无限循环打印hehe和haha,因为代码一往下走就goto跳转回去了,根本结束不了。所以goto经常会跳来跳去,如果一段代码里面利用了很多goto语句,就很可能会让程序执行的正常流程被打断,变得很凌乱,代码就容易出错。我们建议一般情况下少用goto或者不用goto,利用循环也可以很好的解决那些需要goto解决的问题。
下面我们来看看这道题:
写一个关机程序:只要运行起来,电脑就在1分钟内关机,如果输入:我是猪,就取消关机!
在这里我们要先提到一个命令行关机的代码:
shutdown -s -t 60(s是关机的意思,t是多久关机,60是60秒后关机)
知道了命令行关机的代码,我们还需要知道命令行取消关机的代码:
shutdown -a(取消关机)
而C语言中提供了一个函数:system() - 专门执行系统命令的(执行命令行命令的)
即:system("shutdown -s -t 60")和system("shutdown -a")
当我们了解完这两个代码之后,就可以开始写我们的程序了:
#include <string.h>
#include <stdlib.h>
int main()
{
char input[20]= { 0 };//存放输入的信息
system("shutdown -s -t 60");//运行后电脑在一分钟内自动关机
again:
printf("请注意,你的电脑在1分钟内关机,如果输入:我是猪,就取消关机\n");//提示一下
scanf("%s", input);//因为input是数组,已经是地址了,所以不用加&(取地址符号)
if (0 == strcmp(input, "我是猪"))
{
system("shutdown -a");
}
else
{
goto again;//如果输入其他,就利用goto语句重新返回again里面,继续等待关机
}
return 0;
}
我们利用strcmp(string compare)进行两个字符串的对比,这个函数在上面已经介绍过。当两个字符串相同的时候就输出0,符合if条件则取消关机。否则利用goto语句重新跳回again,继续等待关机。strcmp需要引用头文件string.h,而system则需要引用头文件stdlib.h哦。
那我们不是倡导不使用goto语句吗,接下来我们看看使用循环语句要怎么写这个代码。
int main()
{
//关机
char input[20] = { 0 };//存放输入的信息
system("shutdown -s -t 60");
while (1)
{
printf("请注意,你的电脑在1分钟内关机,如果输入:我是猪,就取消关机\n");
scanf("%s", input);
//string.h
if (strcmp(input, "我是猪") == 0)
{
system("shutdown -a");
break;
}
}
return 0;
}
两种方法代码基本上一模一样,但是循环的话我们利用while(1)保证这个循环永远为真,一直循环。所以当strcmp函数通过对比后发现两个字符串一样的时候,我们就要取消关机,然后break跳出整个程序。所以说goto能够做的事情循环也能做,一般情况下我们大部分都使用循环哦。
goto语句的缺点:goto语句只能在一个函数范围内跳转,不能跨函数:
goto跨函数调用会出错的哦,但是循环不会,这也是为什么我们大部分情况都使用循环的原因。
那goto语句难道真的没有用吗?当然不是,多层嵌套的语句我们就会去使用goto语句,这样子就能直接跳出来,不需要多个break,让系统减少运行次数,加快程序速度。
一点小小的恶搞
当我们写完这个自动关机的小程序时,我们可以生成一个exe文件,让你的舍友点开后不得不输入我是猪才能取消关机,要怎么做呢?
如果你使用的是VS2013这个编译软件的话,当我们代码写好后,可以在这里修改一下,改成Release,然后再运行:
运行后我们就可以在这个程序的文件夹里面发现这个文件夹的出现:
点击它,我们就能找到这个exe文件啦:
这就是一个可执行文件,然后你可以把这个文件改个名字发给你的同学,这样子他一点开,就很舒服了,或者说你可以把这个程序偷偷在你同学的电脑里面设置成开机自启动的服务,那么这样子他每次一开机都要输入我是猪才能取消关机,应该是挺好玩的,嘿嘿嘿。
(玩笑而已,请勿过度,被开玩笑的人不觉得是玩笑的话那就不是一件好事情啦)
总结
今天我们将C语言的分支和循环语句完整的讲述了一遍,相信大家都已经能够理解的差不多了吧,我本人也是刚刚开始学习C语言,写这些帖子仅仅是想把自己所学的分享给大家,顺便整理一下自己的知识点,也希望我的文章可以帮助到大家。如果我写的这些有什么错误的话,希望各位能够帮助我指出来,我也好做修改,这也是对我最好的帮助。很感谢能读到这里的你,我们都是最棒的。
最后在这这里附上我的Gitee地址,本章里面所有的代码都在这里有上传,大家有兴趣的话也可以看看我平时练习的代码(我只是一个菜鸟的初学者,求大家轻点喷QAQ)