一篇文章带大家掌握分支以及循环语句的使用以及配合大量练习进行巩固。
分支语句
- if
- switch
循环语句
- while
- for
- do…while
goto语句
1. 什么是语句
C语言中由一个分号;隔开的就是一条语句。 比如:
printf("hehe");
1+2;
2. 分支语句(选择结构)
好好学习,熟练掌握C语言。
不好好学习,一头雾水。
2.1 if语句
那if语句的语法结构是怎么样的呢?
//语法结构:
if(表达式)
语句;
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)
{
printf("未成年\n");
}
}
//代码2
#include <stdio.h>
int main()
{
int age = 0;
scanf("%d", &age);
if(age<18)
{
printf("未成年\n");
}
else
{
printf("成年\n");
}
}
//代码3
#include <stdio.h>
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");
}
}
解释一下: 如果表达式的结果为真,则语句执行。
注:当我们这个练习题做完的时候可以发现,编程代码风格也是很重要的,好的代码风格对于后面的调试以及他人阅读都起到重要的因素。
回归正题,在C语言中如何表示真假?
0表示假,非0表示真。
也就是说即便是负数,-1,-2,-100等,他们都是真
。
如果条件成立,要执行多条语句,怎应该使用代码块。
简单解释一下,代码块也就是
->{}<-
,当我们不适用代码块的时候,我们只能执行一条语句,这对于后面日益复杂的代码显然是不够的。
#include <stdio.h>
int main()
{
if(表达式)
{
语句列表1;
}
else
{
语句列表2;
}
return 0;
}
这里的一对 { }就是一个代码块。
2.1.1 悬空else
当我们写了一个不善于阅读的代码:
#include <stdio.h>
int main()
{
int a = 0;
int b = 2;
if(a == 1)
if(b == 2)
printf("hehe\n");
else
printf("haha\n");
return 0;
}
如果我们熟悉这样的写法的话,对于后面编写大量的代码,可读性肯定降低很多。
改正后:
//适当的使用{}可以使代码的逻辑更加清楚。
//代码风格很重要
#include <stdio.h>
int main()
{
int a = 0;
int b = 2;
if(a == 1)
{
if(b == 2)
{
printf("hehe\n");
}
}
else
{
printf("haha\n");
}
return 0;
}
这样的写法对比第一个例子可读性也是提高很多。
2.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更好,逻辑更加清晰,不容易出错。
2.1.3 练习
当我们了解语法以后,做一个简单的练习吧。
- 判断一个数是否为奇数
- 输出1-100之间的奇数
//第一题
//判断奇数的依据就是:%2是否为0。
int main()
{
int a = 0;
printf("请输一个数:");
scanf("%d", &a);
if (a % 2 != 0)
{
printf("奇数!!!");
}
else
{
printf("偶数!!!");
}
return 0;
}
输出结果:
//第二题
//在第一题的基础上,我们加上循环从1-100依次判断,判断成功就输出。
int main()
{
int i;
for (i = 1; i <= 100; i++)
{
if (i % 2 != 0)
{
printf("%d ", i);
}
}
return 0;
}
输出结果:
2.2 switch语句
switch语句也是一种分支语句。 常常用于多分支的情况。
比如:
输入1,输出星期一
输入2,输出星期二
输入3,输出星期三
输入4,输出星期四
输入5,输出星期五
输入6,输出星期六
输入7,输出星期七
那我没写成if...else if ...else if
的形式太复杂,那我们就得有不一样的语法形式。 这就是switch 语句。
switch(整型表达式)
{
语句项;
}
而语句项是什么呢?
//是一些case语句:
//如下:
case 整形常量表达式:
语句;
2.2.1 在switch语句中的break
在switch语句中,我们没法直接实现分支,搭配break使用才能实现真正的分支。
比如:
#include <stdio.h>
int main()
{
int day = 0;
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;
}
有时候我们的需求变了:
- 输入1-5输出的是“weekday”;
- 输入6-7输出“weekend”
所以我们的代码就应该这样实现了:
#include <stdio.h>
//switch代码演示
int main()
{
int day = 0;
switch(day)
{
case 1:
case 2:
case 3:
case 4:
case 5:
printf("weekday\n");
break;
case 6:
case 7:
printf("weekend\n");
break;
}
return 0;
}
break语句
的实际效果是把语句列表划分为不同的部分。
编程好习惯
在最后一个 case 语句的后面加上一条 break语句。 (之所以这么写是可以避免出现在以前的最后一个 case 语句后面忘了添加 break语句)。
2.2.2 default
如果表达的值与所有的case标签的值都不匹配怎么办?
其实也没什么,结构就是所有的语句都被跳过而已。
程序并不会终止,也不会报错,因为这种情况在C中并不认为适合错误。
但是,如果你并不想忽略不匹配所有标签的表达式的值时该怎么办呢?
你可以在语句列表中增加一条default子句,把下面的标签
default:
写在任何一个case标签可以出现的位置。
当 switch表达式的值并不匹配所有case标签的值时,这个default子句后面的语句就会执行。
所以,每个switch语句中只能出现一条default子句。
但是它可以出现在语句列表的任何位置,而且语句流会像贯穿一个case标签一样贯穿default子句。
编程好习惯
在每个 switch 语句中都放一条default子句是个好习惯,甚至可以在后边再加一个 break 。
2.2.3 练习
#include <stdio.h>
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;
}
大家能猜到结果了嘛,下面由我逐字逐行解释。
首先,n = 1 m = 2,switch(n),也就是从case(1)开始走,case(1)下面执行m++,这里m就变成了3
。
简单回顾:
我们之前说过要想用switch语句实现if…else的分支效果,执行case下面要添加break。
回归话题,case(1)下面没有break,接着执行case2,然后n++,这里n就变成了2
。
接着往下走,也就是case(3),里面switch(n),执行case(2),m++,这里m就变成了4;n++,这里n就变成了3
。
再往下面走遇见了break,那就结束了case(3)下面的switch语句,这里要注意虽然switch里面有break,但他是结束case(3)下面的switch,跟case(3)没有关系,对应的,case(3)它可没有break;也就是需要接着往下走,执行case(4),m++也就变成了5
。
这里可就是有相应的break了,到这里整个switch语句就将结束,我们也拿到了自己想要的答案。
通过这个练习对switch语句的理解也会更加深刻。
总结:
- switch语句是可以嵌套的。
- break只会跳出一层switch。
3. 循环语句
3.1 while循环
回顾一下:
我们已经掌握了,if语句:
if(条件)
执行语句;
当条件满足的情况下,if语句后的语句执行,否则不执行。但是这个语句只会执行一次。
但是我们发现生活中很多的实际的例子是:同一件事情我们需要完成很多次。
那我们怎么做呢? C语言中给我们引入了:while语句,可以实现循环。
//while 语法结构
while(表达式)
循环语句;
比如我们实现:
在屏幕上打印1-10的数字。
#include <stdio.h>
int main()
{
int i = 1;
while(i<=10)
{
printf("%d ", i);
i = i+1;
}
return 0;
}
输出结果:
这个代码已经帮我了解了while语句的基本语法,那我们再了解一下:while语句中的break和continue
3.1.1 while语句中的break和continue
3.1.1.1 break介绍
在前面练习的基础上,当我们要在第5次循环结束的时候停止下来要怎么做?
int main()
{
int i = 1;
while (i <= 10)
{
if (i == 6)
{
break;
}
printf("%d ", i++);
}
return 0;
}
输出结果:
我们可以加上一个判断,当i == 6的时候(也就是根据题意第5次循环结束),就使用break结束循环。
总结: break在while循环中的作用:
其实在循环中只要遇到break,就停止后期的所有的循环,直接终止循环。 所以:while中的break是用于
永久
终止循环的。
3.1.1.2 continue介绍
那我们接下来在换个思路,当我们要在特定的条件下停止下来怎么办呢?比如第5次?
int main()
{
int i = 1;
while (i <= 10)
{
if (i == 5)
//当我们循环当第5次的时候,我们是要使用continue跳过。
//但是需要连带着把i也要加上,这样才能实现真正意义上的跳过第5次循环。
{
i++;
continue;
}
printf("%d ", i++);
//输出打印结果
}
return 0;
}
输出结果:
总结: continue在while循环中的作用就是:
continue是用于终止本次循环的,也就是本次循环中continue后边的代码不会再执行,而是直接
跳转到while语句的判断部分。进行下一次循环的入口判断。
课后拓展
当大家看到这里的时候,我希望能给大家拓展一些新的知识点,也就是
getchar
和putchar
的用法。
从这里我们可以看到,getchar里面是无参的并且返回类型是int,也就是它会有两种结果,一种就是返回字符的ascll码值,还有一种就是返回 -1(也就是EOF
)。
从以上练习我们可以看到,首先
getchar
读取到我们的A,将我们的A以ascll码值
进行存储到变量a
里面,然后我们输出是以字符形式进行输出,自然而然的就输出A了。
那我们接着了解putchar,它的功能相对较为简单,在 C 语言中主要用于输出单个字符。
以上我们简单了解了getchar以及putchar的用法,下面我们再次练习一下巩固知识。
这里涉及EOF可能会有小伙伴不理解什么意思,容我简单解释一下:EOF ->
end of file
(文件结束标志)。
那肯定会有人好奇问了,
scanf
跟getchar
有什么区别呢?
scanf
读取成功,返回的是读取到的数据的个数。
读取失败返回EOF。
getchar
读取成功,返回的是读取到字符的ascll码值。
读取失败返回EOF。
当然getchar的局限性还是很大的,比如我们上面说了读取成功是返回字符的ascll码值,注意这里表达的是字符!!!比如"100""10"它们可不是字符了噢,而是字符串,虽然也能输出,但是需要知道为什么可以输出?
下面我通过更加底层的方法进行解释为什么我们让getchar读取字符串它也能输出出来。
从底层原理来讲,我们输入的字符串是存储在计算机的缓冲区。
getchar
函数的作用是在缓冲区读取到一个字符,将它转换为ascll码值存储在变量中。
所以getchar读取字符以后然后执行循环下的输出字符,这个阶段会持续做一个清除缓冲区的操作,因为我们循环结束条件是 != EOF,所以我们将缓冲区的字符清除完毕,后面我们会看到光标闪烁,它其实是在等待你接下来的输入或者结束。
我们再换一种方法来练习一下,会有些许难度,请认真阅读
为什么我们都没有确认密码,就直接显示确认失败了呢?
原因在于:
当我们输入“123456”的时候,后面我们会敲回车键,这个时候,scanf其实只读取了“123456”,把’\n’留在了缓冲区里面。
当我们下次再有输入的时候,这个’\n’会直接被吸收被赋值给变量。
所以为什么我们scanf都没有自己输入,就直接跳过去判断。
我们需要进行调试验证这个看法是否正确,看看这个input变量里面是否真的存储了’\n’。
从这里我们可以看到,在监视里面,input里面确实存储了‘\n’,在内存中,去查找&input,里面可以看到是:
0a 00 00 00
,转换为十进制也就是10,对应的ascll码值也就是’\n’,由此可以证明以上验证准确无误。
既然我们知道了这种写法的弊端,那我们该怎么修改呢?接着往下面看。
重新通过getchar吸收这个’\n’,但我们不需要赋值给任何变量,即可做到清除了缓冲区里的’\n’。
这样写真的就对了嘛,可不见得,我们看看怎么回事。
好像我们在输入的字符串中间掺杂几个空格的字符,就导致又出现之前的错误了。
接下来我们接着分析错误的地方在哪?老样子我们继续进入调试看看是不是上面说的这个原因。
从这里可以看到我们输入的
123 45 a
这串字符串,只保存了前面的123
。
这会停留在缓冲区的字符串还剩下
45 a
,getchar清除了空格。
input变量也就顺理成章的吸收了还停留在缓冲区中的第一个字符’4’。
那看起来,我们已经找到了错误的原因,接下来我们要怎么修改呢。
由我来解释一下,这段清空缓冲区的代码。
由前面的问题可知:scanf无法输入空格这种特殊代码,所以当我们输入的字符串中带有空格的时候,那么空格以及后面的字符都会被留在缓冲区中。
解决问题:我们需要将缓冲区中的一大串字符都要吸收掉,那么就需要循环吸收,直到清空缓冲区,但是我们不知道留在缓冲区里面的有多少个字符,所以我们需要在while循环里面增加条件,当我们判断到’\n’,条件为假不在进行循环,也就是如图代码。
注:因为我们在判断到==
'\n’的时候,其实getchar也将他吸收了,所以此时缓冲区已经清空。
课外拓展重点总结:
当我们第一个scanf输入时字符或者字符串的时候,下面一个scanf紧跟也是字符或者字符串类型,这个时候我们需要在第一个scanf结束的同时,需要加上清除缓冲区的作用,因为不及时清除会导致\n被下一个scanf进行读取。但是当我们下面一个是其他非字符或者字符串类型的时候,如果后续可能还有字符以及字符串类型输入的话,我们还是可以做一个清除缓冲区操作,谨慎处理。
到此为主,关于getchat
以及putchar
的用法也差不多完结撒花了,我们也花了些许时间进行了解,但我认为这里的付出也是值得的,通过它们的用法以及常规问题,让我们更加深刻的理解它们,下面也是要回归本章正题。
3.2 for循环
我们已经知道了while循环,但是我们为什么还要一个for循环呢? 首先来看看for循环的语法:
3.2.1 语法
for(表达式1;表达式2;表达式3)
循环语句;
表达式1为初始化部分,用于初始化循环变量的。 表达式2 为条件判断部分,用于判断
循环时候终止。 表达式3 为调整部分,用于循环条件的调整。
实际的问题:
使用for循环 在屏幕上打印1-10的数字。
#include <stdio.h>
int main()
{
int i = 0;
//for(i=1/*初始化*/; i<=10/*判断部分*/; i++/*调整部分*/)
for(i=1; i<=10; i++)
{
printf("%d ", i);
}
return 0;
}
现在,我们对比一下for循环以及while循环。
int i = 0;
//实现相同的功能,使用while
i=1;//初始化部分
while(i<=10)//判断部分
{
printf("hehe\n");
i = i+1;//调整部分
}
//实现相同的功能,使用while
for(i=1; i<=10; i++)
{
printf("hehe\n");
}
可以发现在while循环中依然存在循环的三个必须条件,但是由于风格的问题使得三个部分很可能偏离较远,这样查找修改就不够集中和方便。所以,for循环的风格更胜一筹。 for循环使用的频率也最高。
在这里给大家说个小细节,往下看。
这里可以看到,没有代码块的for循环,默认只能执行一条语句。
3.2.2 break和continue在for循环中
我们发现在for循环中也可以出现break和continue,他们的意义和在while循环中是一样的。 但是还是
有些差异:
可以看到在for循环中的break用法跟while循环差不多。
在for循环使用continue不用自己手动改变i的值。
3.2.3 for语句的循环控制变量
一些建议:
- 不可在for 循环体内修改循环变量,防止 for 循环失去控制。
- 建议for语句的循环控制变量的取值采用“前闭后开区间”写法。
//定义变量i
int i = 0;
//前闭后开的写法
for(i=0; i<10; i++)
{}
// --> [0,10)
//两边都是闭区间
for(i=0; i<=9; i++)
{}
// --> [0,9]
说到这里,咱们做一道简单的判断题,往下看。
输出结果:
不知道你有没有猜对,下面由我来分析一下这道题。
首先,我们定义了一个i,j的变量,并将它们初始化为0。
下面,我们创建了一个循环嵌套,这里的关键是这两个的条件一是省略了的。
当i == 0,进入下面for循环,j == 0,j < 4, 执行输出语句,回到调整部分条件3,j++。
当i == 0,进入下面for循环,j == 1,j < 4, 执行输出语句,回到调整部分条件3,j++。
当i == 0,进入下面for循环,j == 2,j < 4, 执行输出语句,回到调整部分条件3,j++。
当i == 0,进入下面for循环,j == 3,j < 4, 执行输出语句,回到调整部分条件3,j++。(这个时候j== 4
)嵌套的for循环部分已经完毕,回到调整部分条件3,i++。
当i == 1,进入下面for循环, 注意!!!这个时候j == 4,也就是说里面的对应里面的循环条件为假,不会在进去执行嵌套的for循环了,回到调整部分条件3,i++。
想必下面你们也就知道该怎么走下去了。
总结:
for循环条件一的省略,导致了条件一没法做到初始化,就产生了循环上的小瑕疵。
3.2.4 一些for循环的变种
#include <stdio.h>
int main()
{
//变种1
for(;;)
{
printf("hehe\n");
}
//变种2
int x, y;
for (x = 0, y = 0; x<2 && y<5; ++x, y++)
{
printf("hehe\n");
}
return 0;
}
3.2.5 一道笔试题
//请问循环要循环多少次?
#include <stdio.h>
int main()
{
int i = 0;
int k = 0;
for(i =0,k=0; k=0; i++,k++)
k++;
return 0;
}
答案是0次。
首先,条件一初始化了i,k = 0;条件二把0赋值给了k变量,也就是k == 0。
条件二是判断部分,判断部分出现一个0是什么情况呢?
没错,条件为假,也就是不会往下面执行程序。
3.3 do…while()循环
3.3.1 do语句的语法
do
循环语句;
while(表达式);
3.3.2 do语句的特点
循环至少执行一次,使用的场景有限,所以不是经常使用。
#include <stdio.h>
int main()
{
int i = 10;
do
{
printf("%d\n", i);
i++;
}while(i<10);
return 0;
}
3.3.3 do while循环中的break和continue
#include <stdio.h>
int main()
{
int i = 10;
do
{
if(5 == i)
break;
printf("%d\n", i);
i++
}while(i<10);
return 0;
}
#include <stdio.h>
int main()
{
int i = 10;
do
{
if(5 == i)
{
i++;
continue;
}
printf("%d\n", i);
i++;
}while(i<10);
return 0;
}
3.4 练习
3.4.1 做题巩固
练习1:
练习2:
循环嵌套不知道大家能不能理解,下面我画一个图,希望能帮助理解。
练习3:
这题主要考点就是利用循环,将arr1的左端以及右端数据赋值到arr2里面(还要注意使用完后left和right往前以及往后移位的问题),在通过循环输出出来,实现如图效果。
3.4.2 折半查找算法
下面我画个图帮助大家理解折半查找的核心代码。
3.4.3 猜数字游戏实现
其实我们要知道,猜数字得核心在于随机,不然一样的数字也没必要继续猜了,那如何产生随机值呢?
我们先得出结论,然后逐字分析:
srand(time(NULL));
int a = rand();
首先,我们先分析time(NULL)什么意思。
官方来讲,time(NULL)返回自 1970 年 1 月 1 日 00:00:00 UTC 以来经过的秒数,由于每次程序运行的时间不同,所以这个值通常也不同。
通俗的来讲,也就是每次运行的程序都不会是在一个时间的,这样我们就产生了不同的时间值。
srand()什么意思呢。
笼统点来说就是,通过里面给的值产生不同的种子值。当我们不给的话,通常会默认同一个,rand就会产生同样的随机值。
rand()也就是通过不同的种子值产生不同的随机值。
那我们总体来说一遍,通过time(NULL)产生不同的时间值,将这个时间值传入srand(),就会利用不同的时间值产生不同的种子值,那么不同的种子值就会产生随机值,而不是运行几次都是一样的值。
代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//菜单
void menu()
{
printf("****************************\n");
printf("***** 1.play 0.exit *****\n");
printf("***** *****\n");
printf("****************************\n");
}
void game()
{
//生成随机数
int ret = rand() % 100 + 1;
int guess = 0;
//循环直到猜中为止
while (1)
{
printf("请输入猜的数字:");
scanf("%d", &guess);
if (guess == ret)
{
printf("恭喜你猜对了!!!\n");
break;
}
else if (guess > ret)
{
printf("猜大了!!!\n");
}
else
{
printf("猜小了!!!\n");
}
}
}
int main()
{
int input = 0;
//项目启动只需要生成一次即可
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
game();
printf("玩游戏!!!\n");
break;
case 0:
printf("退出成功!!!\n");
break;
default:
printf("输入有误,请重新输入!!!\n");
break;
}
} while (input);
return 0;
}
4. goto语句
C语言中提供了可以随意滥用的 goto语句和标记跳转的标号。
从理论上 goto语句是没有必要的,实践中没有goto语句也可以很容易的写出代码。
但是某些场合下goto语句还是用得着的,最常见的用法就是终止程序在某些深度嵌套的结构的处理过程,例如一次跳出两层或多层循环。
这种情况使用break是达不到目的的。它只能从最内层循环退出到上一层的循环。
下面是使用goto语句的一个例子:
一个关机程序
#include <stdio.h>
int main()
{
char input[10] = {0};
system("shutdown -s -t 60");
again:
printf("电脑将在1分钟内关机,如果输入:我是猪,就取消关机!\n请输入:>");
scanf("%s", input);
if(0 == strcmp(input, "我是猪"))
{
system("shutdown -a");
}
else
{
goto again;
}
return 0;
}
而如果不适用goto语句,则可以使用循环:
#include <stdio.h>
#include <stdlib.h>
int main()
{
char input[10] = {0};
system("shutdown -s -t 60");
while(1)
{
printf("电脑将在1分钟内关机,如果输入:我是猪,就取消关机!\n请输入:>");
scanf("%s", input);
if(0 == strcmp(input, "我是猪"))
{
system("shutdown -a");
break;
}
}
return 0;
}
到这里,本章的所有内容都结束,如果你觉得对你有帮助的话,就点赞支持一下吧~
标签:语句,case,int,break,循环,printf,分支 From: https://blog.csdn.net/m0_74445256/article/details/140880371