一、语句的简单概念
1. 语句的分类
C语言中的语句可分为以下5类:
表达式语句、函数调用语句、控制语句、复合语句及空语句
2. 实现程序三大结构
五大语句的组合使用,构成了C语言程序中的三大结构,即顺序结构、选择结构和循环结构。而其中最重要的也是下文围绕展开的就是控制语句
3. 控制语句的分类
条件判断(分支)语句:if else语句、switch语句
循环执行语句:while语句、for语句、do while语句
转向语句:break语句、continue语句、return语句、go to语句
接下来将对上述语句一一进行说明
二、分支语句(选择结构)
“命运不是机遇,而是选择”
1. if else语句
(1)语法结构
- 单独的if语句:
if(表达式)
语句;
- 配合else的if语句:
if(表达式)
语句1;
else
语句2;
- 多分支的if语句:
if(表达式1)
语句1;
else if(表达式2)
语句2;
else
语句3;
- 使用代码块的if语句:
if(表达式)
{
语句列表1;
}
else
}
语句列表2;
{
- 一个简单的示例:
#include <stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
if (n < 18)
printf("未成年");
else
printf("成年");
return 0;
}
运行结果:
(2)常见问题
- 补充一下:
在控制语句中,只有当表达式的结果为真时,语句才执行
那么在C语言中如何表示真假呢?
C语言中规定,以0表示真,以非0表示假,一定要注意是非零表示真!!!而不是只有1才表示真!!!(来自多次踩坑的教训)
注意与表达式的结果区分,表达式若为假,则其结果为0;表达式若为真,则其结果为1. - 正确编写形如1 ≤ x ≤ 5的逻辑关系式:
如下代码,看看当输入20时程序将会输出什么
#include <stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
if (10 <= n <= 18)
printf("√");
else
printf("×");
return 0;
}
按逻辑应该输出“×”,但实际输出如下:
解释:程序先执行条件表达式中逻辑表达式10<=20,结果为真,故该逻辑表达式也就是10<=20的结果就是1,在这个结果的基础上程序接着往下执行,故下一个表达式就变为了1<=18,结果肯定为真,故执行了if语句中的内容,输出了“√”而不是“×”
改正:n>=10 && n <= 18
- else的匹配问题
看看下面的代码会输出什么呢?
#include <stdio.h>
int main()
{
int a = 0;
int b = 1;
if (a == 1)
if (b == 2)
printf("%d", b);
else
printf("%d", a);
return 0;
}
看上去程序好像会输出a,但其实程序运行的结果是什么也不输出
这里就涉及到了else的匹配原则:else和它离的最近的if匹配
故上述代码在执行if(a == 1)
的判断,判断出表达式值为假后,直接执行了程序结束语句return 0
那么该如何避免这样的错误呢?这就是接下来(3)部分重点讲述的内容
(3)代码风格
- 适当使用{}(代码块)增强代码的逻辑性
如(2)中关于else的匹配问题的代码就可写成:
#include <stdio.h>
int main()
{
int a = 0;
int b = 1;
if (a == 1)
{
if (b == 2)
{
printf("%d", b);
}
}
else
{
printf("%d", a);
}
return 0;
}
相比之下,是不是感觉可读性与逻辑性都有所提高呢?对于后面的循环结构也是同样的道理
- 判等表达式的优化
我们在判断一个变量是否等于一个数时通常会这么写:n == 1
其实我们可以把其优化为1 == n
,这样做可以有效避免不小心将判等表达式写成了赋值表达式而产生的一些bug。
比如,本意判断n是否等于1,却不小心写成了if(n = 1)
,如此一来if语句条件表达式的结果就永远为真了;而采用优化后的写法,就算不小心写成了if(1 = n)
,在编译的时候程序也会直接报错,因为1是常量,不能被赋值
2. switch语句
switch语句常用于多分支的情况,如:
输入1-7,分别输出星期一到日
此时若使用if else语句会使得整体代码显得冗长复杂,这就switch来解决这个问题
(1)语法结构
switch(整型表达式)
{
case(整型常量表达式):
语句;
}
其中需要注意的是:switch后面的表达式一定得是整型或字符型(本质是ACSII码值,故也算整型),不能是浮点型;case后面的表达式和指定数组大小类似,一定得是整型的常量表达式,不能是变量。
(2)实现分支
- break语句
搭配上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,输出工作日;输入6-7,输出休息日
代码更改如下:
#include <stdio.h>
int main()
{
int day = 0;
switch (day)
{
case 1:
case 2:
case 3:
case 4:
case 5:
printf("工作日\n");
break;
case 6:
case 7:
printf("休息日\n");
break;
}
return 0;
}
可以看出,break语句的实际效果是把语句列表划分为不同的分支部分
由此也可以得到一个好的编程习惯就是:在最后一个case语句后加上一条break语句。
- default子句
当表达的值与所有的case标签的值都不匹配时,若没有default子句程序就会退出switch分支而继续向下执行;若有default子句,程序就会执行default子句中的内容。
需要注意的是:每个switch分支语句中只能出现一条default子句;default子句在switch语句中的位置可以任意
同样这里也有一个编程的好习惯:在每个switch语句中都放上一条default子句,且可以在子句中加上break。
(3)实际执行
switch语句的实际执行情况可通过如下代码来理解:
#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;
}
程序最终的输出结果是:m=5,n=3
执行过程解释如下:
初始时n=1,故进入switch语句后执行case 1后的语句,执行完毕后,此时m,n的值分别为3,1;
由于在此case后没有break语句,程序不会跳出switch语句,而是继续向下执行case 3后的语句;
进入嵌套的第二个switch后,由于此时n的值还是1,故执行case 1,执行完毕后,m,n分别为3,2;
其后没有break,同理继续执行case 2,执行完毕后m,n分别为4,3;
其后有break,但需要注意此时的break跳出的只是嵌套的这层switch,跳出到外层的switch后还将继续往下执行;
跳出后,执行case 4,执行完毕后,m,n分别为5,3,其后有break,跳出switch语句而到了输出语句,故最后为m=5,n=3。
三、循环语句
1. while语句
“重复(循环)是学习之母”
在if条件语句中我们知道,当条件满足时,执行相应的语句,但是语句只会执行一次。而生活中的大部分事情都需要我们重复地完成许多次,就比如学习一门技术。那我们这么实现这个重复的过程呢?在C语言中,用while语句来实现循环
(1)语法结构
while(表达式)
{
循环语句;
}
(2)实现循环
与分支语句一样,若想正确实现一个循环,就需要借助一些“跳出”工具,在while语句中,它们是break和continue
- while语句中的break
可通过下面这段代码来理解break在while循环中的作用:
#include <stdio.h>
int main()
{
int i = 1;
while (i <= 10)
{
if (i == 5)
break;
printf("%d ", i);
i = i + 1;
}
return 0;
}
这段代码的输出结果是 1 2 3 4
解释:i初始值为1,符合进入循环的条件,循环体中,每循环一次,i的值都在原来基础上加1一次,直到i的值为5时,符合if语句的条件,从而执行break语句,跳出循环,使循环终止
由此可以得到break在while语句中的作用:
while中只要遇到break,就停止之后的所有循环,即直接永久终止了循环(PS:对以下的其他循环语句同理)
- while语句中的continue
可通过下面这段代码来理解continue在while循环中的作用:
#include <stdio.h>
int main()
{
int i = 1;
while (i <= 10)
{
if (i == 5)
continue;
printf("%d ", i);
i = i + 1;
}
return 0;
}
这段代码输出的结果也是1 2 3 4…吗?用编译器运行发现程序在打印1234的后面还有个光标在一直闪烁,代表程序没有正常返回而进入了死循环。
解释:i初始值为1,符合进入循环的条件,循环体中,每循环一次,i的值都在原来基础上加1一次,直到i的值为5时,符合if语句的条件,从而执行continue语句,跳出本次循环,也就是不再执行本次循环中continue语句后的内容而直接跳转至while语句的判断部分,进行下一次循环的判断。如此一来,i的值就锁定在了5,且每次进入循环都会执行continue语句,从而使程序进入了死循环
由此可以得到continue在while语句中的作用:
while中只要遇到continue,就停止本次的循环,即不再执行本次循环之后的内容,而直接准备开始下一次循环(PS:对以下的其他循环语句同理)
2. for语句
(1)语法结构
for(表达式1;表达式2;表达式3)
{
循环语句;
}
- 表达式1
初始化部分,用于初始化循环变量 - 表达式2
条件判断部分,用于判断循环何时终止 - 表达式3
调整部分,用于循环条件的调整
这样的表达式组合在一定程度上就解决了while循环中这三部分由于代码内容而隔了较远而不便修改的问题 - 一个简单的运用示例:
打印1到10的数字
#include <stdio.h>
int main()
{
int i = 0;
for (i = 1; i <= 10; i++)
{
printf("%d ", i);
}
return 0;
}
(2)for循环中的break与continue与while对比
如上while语句的介绍所说,作用是相同的,但由于循环三部分的位置不同会有略微的差异,可通过下面两段代码来对比理解它们的不同:
//代码1
#include <stdio.h>
int main()
{
int i = 0;
for (i = 1; i <= 10; i++)
{
if (i == 5)
break;
printf("%d ", i);
}
return 0;
}
//代码2
#include <stdio.h>
int main()
{
int i = 0;
for (i = 1; i <= 10; i++)
{
if (i == 5)
continue;
printf("%d ", i);
}
return 0;
}
代码1的输出结果和使用while循环一样,为1 2 3 4
代码2就不会想while循环那样输出1 2 3 4然后进入死循环,代码2的输出结果为1 2 3 4 6 7 8 9 10
产生这样的差异的原因就在于循环执行的方式的不同:while循环中的循环条件调整部分作为一条语句写在了continue之后,故遇到continue终止本次后,调整部分的值就被锁定了,从而造成了死循环;而在for循环,无论是正常执行完本次循环还是continue终止了本次循环,循环条件调整部分的语句都会执行,自然也就不会像while语句那样进入死循环
(PS:若想用while语句但又担心产生上述问题可以将调整部分写到判断部分中,不过只能应对一部分的情况,如n=10; while(n--)
等)
(3)有关for循环的变种
- 省略写法
如:
for (; ;)
{
printf("Hello");
}
如上代码将会无限循环打印Hello
判断部分省略即判断部分恒为真
再如:
//代码1
int i = 0;
int j = 0;
for (i = 0; i < 10; i++)
{
for (j = 0; j < 10; j++)
{
printf("hehe\n");
}
}
代码1会打印100个hehe
若省略掉初始化部分,这里打印多少个hehe?
//代码2
int i = 0;
int j = 0;
for (; i < 10; i++)
{
for (; j < 10; j++)
{
printf("hehe\n");
}
}
结果是10个.
解释:相较于代码1,当i=0循环结束i++为1进行第二次循环时,j并没有再次初始化为0而仍为第一次循环结束时的10,也就是内循环不再进行
- 使用多于一个变量控制循环
如下代码:
for (x = 0, y = 0; x<2 && y<5; ++x, y++)
{
printf("hehe\n");
}
结果将会打印2个hehe
(4)注意事项
- 不可在for 循环体内修改循环变量,防止 for 循环失去控制
- 不建议for循环的省略写法,容易导致问题
(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次
解释:注意判断部分是k = 0(赋值表达式),而不是k == 0(判等表达式),而对于赋值表达式,赋值多少表达式的结果就是多少,故表达式k = 0的结果为0(假),不进入循环。
3. do while语句
(1)语法结构
do
{
循环语句;
}while(表达式);
注意while后面还有一个分号!
(2)执行特点
先执行一次循环体中的内容再进行条件判断看是否继续执行循环,也就是循环至少执行一次。由于使用场景有限,所以不是经常用
(3)do while中的break与continue
作用同while循环和for循环一样,与while循环的差别就在于do while会先执行一次循环体中的内容,而在这之后循环的执行就和while一模一样了
4. go to语句
(1)简单说明
C语言中提供了可以随意滥用的 goto 语句和标记跳转的标号。go to语句不是必需的,实际中大部分情况不用go to语句也可以很容易地写出代码。go to语句的使用不当很可能导致bug横生,故一般的建议是少用。附上一张在别的博客看到的一张关于go to语句的有趣梗图:
当然,在某些场合下goto语句还是用得着的,最常见的用法就是终止程序在某些深度嵌套的结构的处理过程。例如:一次跳出两层或多层循环。此时若用break则可能要较多条break语句才能实现,而可以只用一条go to语句进行跳出
(2)适合场景
goto语言真正适合的场景如下:
for (...)
for (...)
{
for (...)
{
if (disaster)
goto error;
}
}
…
error :
if (disaster)
// 处理错误情况
如上程序,当遇到goto语句时,程序就会跳转到相应标志处(也就是error),并执行标志处后面的内容,在如上代码中就体现为处理错误情况。
(PS:goto语句只能在一个函数范围内跳转,不能跨函数)
本章完。
看完觉得有觉得帮助的话不妨点赞收藏鼓励一下,有疑问或有误地方的地方还恳请过路的朋友们留个评论,多多指点,谢谢朋友们!🌹🌹🌹