C语言的分支和循环
前言
C语言是结构化的程序设计语⾔,这⾥的结构指的是顺序结构、选择结构、循环结构,C语言是能够实现这三种结构的。如果我们仔细分析,我们日常所见的事情都可以拆分为这三种结构或者这三种结构的组合。
我们可以使⽤if
、switch
实现分支结构,使⽤for
、while
、do while
实现循环结构。
分支结构
if 语句
if语句
if
语句的语法规则:
if (表达式)
语句
若表达式为真,则执行语句;若表达式为假,则不执行语句。
表达式的值为0,则不执行语句;表达式的值为非0,则执行语句。
大家可能经常看到if (a==1)
这样的语句,这样的表达式是否还符合上面的规则呢?答案是肯定的。a==1
是一个判断语句,如果a==1,则表达式返回值 1,如果a!=1,则表达式返回值 0。也就是说,判断语句的判断结果是通过返回值来反映的,返回值为1(真),则语句正确;返回值为0(假),则语句不正确。
所以这就引出了一个常见的错误,初学者可能会不小心写成if (a=1)
,仔细看看这两个语句有什么不同,这样写会导致什么后果?如果这样写的话,会导致a的值被赋为1,且语句必定执行。为什么呢?因为括号中是一个赋值语句,将a的值赋为1,然后if
判断1为真,执行语句的。如果a被赋为非0的值,则语句一定会执行,如果a被赋为0,则语句一定不会执行,if
语句就失去了选择分支的作用。
下面举一个简单的例子:输入一个整数,判断是否为奇数。
include <stdio.h>
int main()
{
int num = 0;
scanf("%d", &num);
if(num % 2 == 1)
printf("%d 是奇数\n", num);
return 0;
}
else 和 else if 语句
if
语句如果不成立,可以继续判断多个分支条件,这时就需要用到else
和else if
语句。
else if
的使用规则和if
相同,在前面的if
和else if
都不成立的情况下会执行判断。
else
后面不跟语句,在前面的if
和else if
都不成立的情况下直接执行。
需要注意的是,C语言中的if
、else if
和else
的后面都只能跟一个语句。
#include<stdio.h>
int main()
{
int a;
scanf("%d", &a);
if (a % 2 == 0)
printf("%d是偶数", a);//①
printf("------------");//②
else
printf("%d是奇数", a);//③
printf("************");//④
return 0;
}
大家可能认为如果a是偶数,就会执行语句①②,但是这段代码是执行不了的,else
找不到if
,编译器会报错。因为if
后面只能跟一个语句,所以判断完a是偶数,就执行语句①,然后见到了语句②,也会执行,此时的if
语句就已经结束了,因为他在执行完语句①后没有遇到else if
和else
,所以他就认为结束了,这时当程序执行到else
时,就会提示缺少语句,在没有if
的情况下使用了else
。
再来一个例子证明一下
int main()
{
int a;
scanf("%d", &a);
if (a % 2 == 0)
printf("%d是偶数\n", a);①
printf("——————\n");②
return 0;
}
可以看到,在没有进入if
语句的情况下,还是执行了语句②,所以if
语句后面只能控制一条语句。
如果想让if
语句后面可以跟多条语句的话,我们可以把多个语句塞进一个语句块内,把语句块作为if
的执行语句,就可以实现执行多个语句的效果了(语句块就是用"{ }"把多个语句括起来)。同理,else if
也是一样。
int main()
{
int a;
scanf("%d", &a);
if (a % 2 == 0)
{
printf("%d是偶数\n", a);
printf("——————\n");
}
else if (a % 2 != 0)
{
printf("%d是奇数\n", a);
printf("************\n");
}
return 0;
}
悬空 else 问题
如果有多个if
和else
,可以记住这样⼀条规则,else
总是跟最接近的if
匹配,而与缩进无关。
int main()
{
int a = 0;
int b = 2;
if(a == 1)
if(b == 2)
printf("执行语句一\n");
else
printf("执行语句二\n");
return 0;
}
大家可以猜一猜上面代码的运行结果,大家可能会认为,a != 0,应该执行语句二,但其实正确的结果是,语句一和语句二都不执行。
这是因为else
总是跟最接近的if
匹配,与缩进无关,所以在第一个if
判断为假后,会直接跳到return 0
。
如果我们像让代码按照我们刚刚想的那样运行该怎么办呢?很简单,用{}。
int main()
{
int a = 0;
int b = 2;
if(a == 1)
{
if(b == 2)
printf("hehe\n");
}
else
printf("haha\n");
return 0;
}
这样就可以使else
和第一个if
配对了。
所以在写代码时要多使用大括号,不仅可以减少bug的出现,也会使代码更清晰,便于调试。
关系运算符
C 语言用于比较的表达式,称为“关系表达式”,里面使用的运算符就称为“关系运算符”,主要有下面6个。
>
<
>=
<=
==
!=
分别对应大于、小于,大于等于、小于等于、等于、不等于。
关系表达式通常返回0或1,表示真假。
如果判断式成立,则为真,返回1;如果判断式不成立,则为假,返回0。
这样就导致关系判断式不能连写,如:i<j<k。这样的判断式是符合语法规则的,编译器不会报错,但是它的功能并不是我们想象的那样。
比如下面的例子:
int main()
{
int age = 10;
if(18<=age<=36)
{
printf("⻘年\n");
}
return 0;
}
10<18,大家可能会认为该程序什么都不会输出,但实际运行的话,该程序会执行输出语句。
因为在执行if (18<=age<=36)
时,按照由左往右的顺序,先由10和18比较,18<=age为假,返回0,然后由0和36作比较,0<=36为真,返回1,则执行输出语句。
条件操作符
条件操作符也叫三目操作符,需要接受三个操作数,形式如下:
exp1 ? exp2 : exp3
条件操作符的计算逻辑是:如果exp1
为真,exp2
计算,计算的结果是整个表达式的结果;如果exp1
为假,exp3
计算,计算的结果是整个表达式的结果。
如下代码:
int main()
{
int a = 0;
int b = 0;
scanf("%d", &a);
if (a > 5)
b = 3;
else
b = -3;
printf("%d\n", b);
return 0;
}
如果使用条件操作符表示:
int main()
{
int a = 0;
int b = 0;
scanf("%d", &a);
b = a>5 ? 3:-3;
printf("%d\n", b);
return 0;
}
例2:使用条件操作符找出两个数中的较大值
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int m = a>b ? a : b;
printf("%d\n", m);
return 0;
}
逻辑操作符
逻辑运算符提供逻辑判断功能,用于构建更复杂的表达式,主要有下面三个运算符。
! :逻辑取反运算符(改变单个表达式的真假)。
&& :与运算符,就是并且的意思(两侧的表达式都为真,则为真,否则为假)。
|| :或运算符,就是或者的意思(两侧至少有⼀个表达式为真,则为真,否则为假)。
这里的&
和与门的简写是一样的,功能也是一样的,或门的功能在C语言中由||
来表示,非门由!
表示。
例:写一个判断是否是闰年的程序
//代码1
int main()
{
int year = 0;
scanf("%d", &year);
if(year%4==0 && year%100!=0)
printf("是闰年\n");
else if(year%400==0)
printf("是闰年\n");
return 0;
}
//代码2
int main()
{
int year = 0;
scanf("%d", &year);
if((year%4==0 && year%100!=0) || (year%400==0))
printf("是闰年\n");
return 0;
}
短路
C语言逻辑运算符还有⼀个特点,它总是先对左侧的表达式求值,再对右边的表达式求值,这个顺序是
保证的。如果左边的表达式满足逻辑运算符的条件,就不再对右边的表达式求值。这种情况称为“短路”。
“短路”会导致右边的表达式不执行,有时会对程序的结果产生影响,且这种bug不容易被发现,在使用&&
和||
时,需小心短路现象。
例:请写出如下代码的运算结果
int main()
{
int i = 0,a=0,b=2,c =3,d=4;
i = a++ && ++b && d++;
printf("i = %d\na = %d\nb = %d\nc = %d\nd = %d\n",i, a, b, c, d);
return 0;
}
i,a,b,c,d的值分别为0,1,2,3,4.
因为在判断a++&&…时,a为0,则发生短路现象,后面的表达式不再计算,并将0赋给i,然后a+1。
int main()
{
int i = 0,a=0,b=2,c =3,d=4;
i = ++a && ++b && d++;
printf("i = %d\na = %d\nb = %d\nc = %d\nd = %d\n",i, a, b, c, d);
return 0;
}
i,a,b,c,d的值分别为0,1,2,3,5。分析同上。
int main()
{
int i = 0,a=0,b=2,c =3,d=4;
i = a++ || ++b || d++;
printf("i = %d\na = %d\nb = %d\nc = %d\nd = %d\n",i, a, b, c, d);
return 0;
}
i,a,b,c,d的值分别为1,1,3,3,4.
因为在判断a++&&++b时,b为3,则发生短路现象,后面的表达式不再计算,并将1赋给i,然后a+1。
int main()
{
int i = 0,a=0,b=2,c =3,d=4;
i = ++a || ++b || d++;
printf("i = %d\na = %d\nb = %d\nc = %d\nd = %d\n",i, a, b, c, d);
return 0;
}
i,a,b,c,d的值分别为1,1,2,3,4。分析同上。
switch 语句
switch
语句是⼀种特殊形式的if...else
结构,用于判断条件有多个结果的情况。它把多重的else if
改成更易用、可读性更好的形式。
switch
语句的语法规则
switch (expression)
{
case value1: statement
case value2: statement
default: statement
}
上面代码中,根据表达式expression
不同的值,执行相应的case
分支。如果找不到对应的值,就执行default
分⽀。
需要注意的是:switch
语句后面的表达式必须是整数类型,case
后面的常量表达式也必须是整型常量表达式。同时,每一个case
语句的最后应有break
不然不会跳出seitch
,而会继续运行下一个case
。
if 语句和 switch 语句的对比
理论上,switch
语句能够完成的语句,if
语句都能完成,只是在特定情况下,switch
语句比if
语句更简洁。
例:输入任意⼀个整数值,计算除3之后的余数。
如果使用if
语句完成,如下:
int main()
{
int n = 0;
scanf("%d", &n);
if(n%3 == 0)
printf("整除,余数为0\n");
else if(n%3 == 1)
printf("余数是1\n");
else
printf("余数是2\n");
return 0;
}
如果使用switch
语句改写,就可以是这样的:
int main()
{
int n = 0;
scanf("%d", &n);
switch(n%3)
{
case 0:
printf("整除,余数为0\n");
break;
case 1:
printf("余数是1\n");
break;
case 2:
printf("余数是2\n");
break;
}
return 0;
}
上述的代码中,我们要注意的点有:
case
和后边的数字之间必须有空格- 每⼀个
case
语句中的代码执行完成后,需要加上break
,才能跳出这个switch
语句。
switch 语句中的 default
在使用switch
语句的时候,我们经常可能遇到⼀种情况,比如switch
后的表达式中的值无法匹配代码中的case
语句的时候,这时候要不就不做处理,要不就得在switch
语句中加入default
子句。
在switch
语句中case
字句和default
子句没有顺序要求 ,只要顺序是满足实际需求的就可以。不过一般把default
子句放在最后处理。
循环结构
while 语句
while
语句的语法结构和if
语句非常相似。
while
语句的语法规则:
while(表达式)
语句;
和if
语句一样,while
后面也只能跟一个语句,如果想实现多行代码的循环,可以使用大括号。
首先上来就是执行判断表达式,表达式的值为0,循环直接结束;表达式的值不为0,则执行循环语句,语句执行完后再继续判断,是否进行下⼀次判断。
例:输入⼀个正的整数,逆序打印这个整数的每⼀位
例如:输入:1234,输出:4 3 2 1
题目解析
- 要想得到n的最低位,可以使用n%10的运算,得到的余数就是最低位,如:1234%10得到4
- 要想去掉n的最低位,找出倒数第⼆位,则使用 n=n/10 就可以去掉最低位,如:n=1234/10得到123,123相较于1234就去掉了最低位,123%10就得到倒数第⼆位3。
- 循环1和2两个步骤,在n变成0之前,就能到所有的位。
int main()
{
int n = 0;
scanf("%d", &n);
while(n)
{
printf("%d ", n%10);
n /= 10;
}
return 0;
}
for 语句
for
循环是三种循环中使⽤最多的,for
循环的语法形式如下:
for(表达式1; 表达式2; 表达式3)
语句;
表达式1 用于循环变量的初始化
表达式2 用于循环结束条件的判断
表达式3 用于循环变量的调整
首先执行表达式1初始化循环变量,接下来就是执行表达式2的判断部分,表达式2的结果如果==0,则循环结束;表达式2的结果如果!=0则执行循环语句,循环语句执行完后,再去执行表达式3,调整循环变量,然后再去表达式2的地方执行判断,表达式2的结果是否为0,决定循环是否继续。
整个循环的过程中,表达式1初始化部分只被执行1次,剩下的就是表达式2、循环语句、表达式3在循环。
例:在屏幕上打印1~100的值
int main()
{
int i = 0;
for(i=1; i<=100; i++)
{
printf("%d ", i);
}
return 0;
}
do while 语句
do whlie
语句的语法规则
do{
语句
}while(表达式);
在do while
循环中先执行图上的“语句”,执行完语句,再去执行“判断表达式”,判断表达式的结果是!=0,则继续循环,执行循环语句;判断表达式的结果==0,则循环结束。
所以在do while
语句中循环体是至少执行⼀次的,这是do while
循环比较特殊的地方。
注意do while
循环中的while
语句的最后有;
。
例:在屏幕上打印1~100的值
int main()
{
int i = 1;
do
{
printf("%d ", i);
i = i + 1;
}while(i<=10);
return 0;
}
break 和 continue 语句
在循环执行的过程中,如果某些状况发生的时候,需要提前终止循环,这是非常常见的现象。C语言中提供了break
和continue
两个关键字,就是应用到循环中的。
• break
的用是用于永久的终止循环,只要break
被执行,直接就会跳出循环,继续往后执行。
• continue
的作用是跳过本次循环continue
后边的代码,在for
循环和while
循环中有所差异。
while 循环中的 break 和 continue
break
例:
int main()
{
int i = 1;
while(i<=10)
{
if(i == 5)
break;//当i等于5后,就执⾏break,循环就终⽌了
printf("%d ", i);
i = i+1;
}
return 0;
}
执行结果
打印了1,2,3,4后,当i等于5的时候,循环在break
的地方终止,不再打印,不再循环。所以break
的作用就是永久的终止循环,只要break
被执行,break
外的第⼀层循环就终止了。
以后我们在循环中,想在某种条件下终止循环,则可以使用break
来完成我们想要的效果。
continue
continue
是继续的意思,在循环中的作用就是跳过本次循环中continue
后边的代码,继续执行下⼀次循环的判断。
例:
int i = 1;
while(i<=10)
{
if(i == 5)
continue;
//当i等于5后,就执⾏continue,直接跳过continue的代码,去循环的判断的地⽅
//因为这⾥跳过了i = i+1,所以i⼀直为5,程序陷⼊和死循环
printf("%d ", i);
i = i+1;
}
return 0;
}
到这里我们就能分析出来,continue
可以帮助我们跳过某⼀次循环continue
后边的代码,直接到循环的判断部分,进行下⼀次循环的判断,如果循环的调整是在continue
后边的话,可能会造成死循环。
for 循环中的 break 和 continue
break
和while
循环中的break
⼀样,for
循环中的break
也是用于终止循环的,不管循环还需要循环多少次,只要执行到了break
,循环就彻底终止。
例:
int main()
{
int i = 1;
for(i=1; i<=10; i++)
{
if(i == 5)
break;
printf("%d ", i);
}
return 0;
}
执行结果
continue
例:
int main()
{
int i = 1;
for(i=1;i<=10;i++)
{
if(i == 5)
continue;
//当i等于5后,就执⾏continue,直接跳过continue的代码,去循环的判断的地⽅
printf("%d ", i);
}
return 0;
}
执行结果
所以在for
循环中continue
的作用是跳过本次循环中continue
后的代码,直接去到循环的调整部分。未来当某个条件发生的时候,本次循环无需再执行后续某些操作的时候,就可以使用continue
来实现。
continue 在 while 和 for 循环中的区别
使用continue
都可以使程序跳过本次循环,不同的是,在while
语句中,回跳到判断语句,容易跳过自增而导致死循环,可以把自增条件放在continue
前面来避免;而在for
语句中,会跳到自增语句,然后再执行判断,避免了死循环。
do while 循环中的 break 和 continue
do while
语句中的break
和continue
的作用和while
循环中几乎⼀模⼀样。
循环的嵌套
while
,do while
,for
这三种循环往往会嵌套在⼀起才能更好的解决问题,就是:循环嵌套。
例:找出100~200之间的素数,并打印在屏幕上。(素数⼜称质数,只能被1和本⾝整除的数字。)
题目解析:
- 要从100-200之间找出素数,首先得有100-200之间的数
- 假设要判断i是否为素数,需要拿(2,i-1)之间的数字去试除i,需要产生(2,i-1)之间的数字,也可以使用循环解决。
- 如果(2,i-1)之间有数字能整除i,则i不是素数,如果都不能整除,则i是素数。
int main()
{
int i = 0;
//循环产⽣100~200的数字
for(i=100; i<=200; i++)
{
//判断i是否为素数
//循环产⽣2~i-1之间的数字
int j = 0;
int flag = 1;//假设i是素数
for(j=2; j<i; j++)
{
if(i % j == 0)
{
flag = 0;
break;
}
}
if(flag == 1)
printf("%d ", i);
}
return 0;
}
这是最容易想到的做法,但其实该做法还可以进行一些小优化。
- 因为素数一定不可能是偶数,所以可以从101开始判断,且只判断奇数
- 如果在进行取余操作时,到(i的开方)没有出现余数等于0的情况,则不用判断后面的i了
所以优化后上述代码为:
#include<math.h>
int main()
{
int i = 0;
for(i=101; i<=200; i+=2)//只产生奇数
{
//判断i是否为素数
//循环产⽣ 3~根号i 之间的数字
int j = 0;
int flag = 1;//假设i是素数
for(j=3; j<sqrt(i); j++)
{
if(i % j == 0)
{
flag = 0;
break;
}
}
if(flag == 1)
printf("%d ", i);
}
return 0;
}
goto 语句
C语言提供了⼀种非常特别的语法,就是goto
语句和跳转标号,goto
语句可以实现在同⼀个函数内跳转到设置好的标号处。
例:
int main()
{
printf("执行语句一\n");
goto next;
printf("执行语句二\n");
next:
printf("跳过了语句二的执行\n");
return 0;
}
执行结果
goto
语句如果使用的不当,就会导致在函数内部随意乱跳转,打乱程序的执行流程,所以建议是能不用尽量不去使用;但是goto
语句也不是一无是处,在多层循环的代码中,如果想快速跳出使⽤goto
就非常的方便了。
for(...)
{
for(...)
{
for(...)
{
if(disaster)
goto error;
}
}
}
error:
....
本来for
循环想提前退出得使用break
,⼀个break
只能跳出⼀层for
循环,如果3层循环嵌套就得使用3个break
才能跳出循环,所以在这种情况下使用goto
语句就会更加的快捷。
结语
分支(或者说选择)和循环语句在C语言中非常重要,只有多写才能检验自己的不足,我在写代码的时候经常会犯各种小错误,比如文中提到的do while
语句中没加;
,while
语句的判断条件写成赋值等等各种错误,这类错误一眼看上去很难看出来,需要取一句一句调试,所以尽量还是在写代码的时候就规避这些错误,养成良好的写代码习惯。