看完上一篇文章,我们对C语言要学习的一个整体知识框架有了一个了解,接下去,我们将进入分支和循环语句的学习,Are you ready?
一、什么是语句?
- 首先我们来说说什么是语句,在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)
为了让其在一行打印,不需要一行行地打印,我们可以使用这样一个命令
system("cls"); //清屏,一行打印
- 但是这个system()函数要加上头文件【#include <stdlib.h>】,大家不要忘了。我们加上这条语句后再来看看运行效果是怎样的
video(video-Q24mZ0io-1666671888836)(type-csdn)
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: