目录
前言
通过这篇博客我们来详细的介绍分支语句和循环语句
我们要知道C语言是结构化的程序设计语言,这里的结构化是什么意思呢?通常我们写C程序会写出三种结构:
- 顺序结构
- 循环结构
- 选择结构
计算机语言高度抽象了我们生活中的事情,通过一个计算机语言便能表达我们生活中的问题,以此来便捷的帮助我们解决生活中的问题。
- 分支语句
if
switch
- 循环语句
while
for
do while
- goto语句
一、 什么是语句?
C语言中由一个分号 ; 隔开的就是一条语句,如下:
int main()
{
28;
int a = 5;
; //空语句
return 0;
}
二、分支语句(选择结构)
选择结构C语言是如何描述它的呢?
1.if语句
语法结构
//1.
if (表达式)
语句;
//2.
if (表达式)
语句1;
else
语句2;
//3.
//多分支
if(表达式1)
语句1;
else if(表达式2)
语句2;
else
语句3;
通过以下代码来体验一下:
代码1
int main()
{
int age = 8;
if (age < 18)
{
printf("未成年人\n");
}
return 0;
}
这里的if语句会对后面圆括号里面的表达式进行判断,表达式为真则执行语句,表达式为假,则不执行。这里age<18显然成立,所以语句被执行。
运行结果:
代码2
int main()
{
int age = 28;
if (age < 18)
{
printf("未成年人\n");
}
else
{
printf("成年人\n");
}
return 0;
}
这里的if语句后面条件为假,所以就不执行if后面的语句,而是执行else后面的语句
运行结果:
代码3
int main()
{
int age = 35;
if (age < 16)
{
printf("少年\n"); //语句1
}
else if (age <= 30)
{
printf("青年\n"); //语句2
}
else if (age <= 50)
{
printf("中年\n"); //语句3
}
else //这最后一个else也可以省略不写
{
printf("老年\n"); //语句4
}
return 0;
}
运行结果:
这段代码中的if是多分支的,首先判断条件,age<16不成立,所以就不执行语句1,然后判断age是否在16到30之间,也不在,不执行语句2,再判断age是否再30到50之间,满足条件,所以这里就执行了语句3,后面的语句不再执行。
错误写法
- if语句中的判断条件使用连续比较的形式 eg.if(16 <= age<=30) ,这样写的话,其实就和我们想表达的意思不一样了,这里的判断条件的含义就是先判断16<=age是否成立,发现成立,所以条件为真,然后判断条件就变成了 1<=30 是否成立,发现成立,执行相应的语句
- 注意if语句中如果想要在条件为真或为假时同时执行多条语句,要用花括号括起来,如下:
错误写法:
#include <stdio.h>
int main()
{
int age = 15;
if (age < 18)
printf("未成年人\n");
printf("要听话\n");
else
printf("成年人\n");
printf("做事要考虑后果\n");
return 0;
}
正确写法:
#include <stdio.h>
int main()
{
int age = 15;
if (age < 18)
{
printf("未成年人\n");
printf("要听话\n");
} //多条语句同时执行,要用花括号
else
{
printf("成年人\n");
printf("做事要考虑后果\n");
}
return 0;
}
运行结果:
再啰嗦一句,在C语言中表示真假
0----------假
非0-------真
悬空else问题
首先请看如下代码,想想程序运行结果是什么?
#include <stdio.h>
int main()
{
int a = 0;
int b = 2;
if(a == 1)
if(b == 2)
printf("hello\n");
else
printf("goodbye\n");
return 0;
}
运行结果是hello? goodbye? 还是…… 我们来验证一下
结果是什么也没有,这是为什么呢?
要注意,这段代码里面有一个else,两个if,else与离它最近的的if进行匹配,即与if(b==2)进行匹配, 那么这对if…else整体相当于是第一个if(a==1)的条件为真时的执行语句,而a==1为假,所以后面的这对if…else不会被执行,直接来到了return 0,所以运行结果就是什么也没有
这段代码本身也是一个坑,故意把else和第一个if对齐,让我们误以为它们俩确实是匹配的,所以我们平时在写代码的时候也一定要注意代码该缩进的就要缩进,不该缩进的不要随意缩进,也不要随意省略花括号
改正之后(也可以有其他的修改方法,看自己具体想要实现的是什么):
#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;
}
if书写形式的对比
代码1:
if (condition)
{
return x;
}
return y;
这段代码的功能就是如果条件成立,返回x,如果条件不成立,返回y。但是在这里我们可能直观的看上去第一感觉就是它并不是这样的功能,容易被误解,这是不好的代码风格
代码2:
if(condition)
{
return x;
}
else
{
return y;
}
这段代码与代码1相比就好了很多,一眼看过去就知道它想干啥,要知道代码风格是很重要的
代码3:
#include <stdio.h>
int main()
{
int a = 5;
if (a = 8) //bug
{
printf("hello");
}
return 0;
}
本来是想判断是否等于8,但写出来却成了将8赋给a,这样一来,判断条件成了永真条件了,程序也能成功运行,后期不容易找到bug。这种错误很多人都会犯,而且找bug的时候不容易被发现
可以通过以下代码来有效避免这种情况的发生
#include <stdio.h> int main() { int a = 5; if (8 == a) //将8写在前面 { printf("hello"); } return 0; }
这样的话如果你代码写错写成8=a,的话,编译器就会报错,bug也很容易找到
#include <stdio.h>
int main()
{
int a = 5;
if (8 = a)
{
printf("hello");
}
return 0;
}
代码直接运行不起来,错误直接被显示出来,这样也会比较容易去改错
2.switch语句
switch语句主要应用于多分支的情况,多分支的情况用if…else就过于繁琐。
eg.
输入1,输出星期一
输入2,输出星期二
输入3,输出星期三
输入4,输出星期四
输入5,输出星期五
输入6,输出星期六
输入7,输出星期七
语法结构
switch(整型表达式)
{
语句项;
}
首先通过以下代码来体验一下:
#include <stdio.h>
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;
}
运行结果
现在发现,我们本来是想输入2,然后让它就输出星期二,但结果却是从星期二一直打印到了星期天,这是为什么呢?主要是因为我们忽略了switch语句的终止,应该加上break,跳出switch,做如下修改:
#dinclude <stdio.h>
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;
}
这样它就只执行case 2后面的语句
要注意:
- switch后面的括号里必须为整型表达式
- 每一个case都是一个语句项
- case后面必须是常量表达式,不能出现变量
- 在switch语句中如果没有break,它从哪里进去,就从哪里一直执行下去,所以使用switch语句的时候要注意,代码要从哪里跳出就要加break
什么是语句项?
语句项就是一些case语句:eg.
case 整型常量表达式:
语句;
在switch语句中的 break
在switch语句中,我们没法直接实现分支,搭配break使用才能实现真正的分支
那么现在如果我们想要在输入1-5之间的数字的时候输出工作日,输入6-7的时候输出休息日,就可以来实现了,如下代码:
#include <stdio.h>
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;
}
但是这样做的话又会有些麻烦,从case1到case5执行的都是同一条语句,那么其实我们可以这样来简化代码
#include <stdio.h>
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;
}
运行结果:
这就相当于将代码分块了,一块执行同一种情况,到这里也就能理解了,break语句的实际效果是把语句列表划分为不同的部分
温馨提示:
- 在最后一个case语句的后面建议也加上break
- switch语句支持qi
default子句
在switch语句当中,可以根据后面整型表达式的值去执行相应的case语句,那如果整型表达式的值没有相对应的case语句呢?程序并不会终止,也不会报错,因为这种情况在C中并不认为是个错误,但是在这里还是建议要加上 default,也可以在break后面再加上break,这样更好
当 switch表达式的值并不匹配所有case标签的值时,这个default子句后面的语句就会执行。
所以每个switch语句中只能出现一条default子句。
default语句在符合逻辑的情况下可以放在switch语句的任意位置
如下代码:
#include <stdio.h>
int main()
{
int a = 0;
scanf("%d", &a);
switch (a)
{
case 1:
printf("666\n");
break;
case 2:
printf("233333\n");
break;
default:
printf("输入错误\n");
break;
}
return 0;
}
练习题
#include <stdio.h>
int main()
{
int n = 1;
int m = 2;
switch (n)
{
case 1:
m++;
case 2:
n++;
case 3:
switch (n)
{
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进入switch语句里面从case1开始执行,case1和case2后面都没有break,所以一直执行,执行完case1和case2之后m==3,n==2,然后进入case3,这里嵌套使用了switch,里面的switch从case2开始执行,执行完之后m==4,n==3,遇到break之后跳出里面嵌套的break,然后执行外层switch语句的case4,m==5,n==3,然后从switch里面出来,进行结果打印
运行结果:
三、 循环语句
- while
- for
- do…while
同一件事情重复执行,在C语言中就是用循环语句
1.while语句
语法结构
while(表达式)
循环语句;
执行流程
那如果这里我们想要打印1--10的数字怎么写?
代码如下:
#include <stdio.h>
int main()
{
int i = 1;
while (i <= 10)
{
printf("%d ",i);
i++;
}
return 0;
}
代码分析:
- 这里while后面的括号里面是循环终止的判断条件,条件为真,继续循环;条件为假,终止循环
- i++是一个累加器,用来改变i的值,每循环一次,i加1,并且会打印出i的值
运行结果:
while语句中的continue
请看如下代码
#include <stdio.h>
int main()
{
int i = 1;
while (i <= 10)
{
if (i == 5)
continue;
printf("%d ", i);
i = i + 1;
}
return 0;
}
加入continue之后运行结果又是什么呢?
分析:
这里的if语句进行判断当i==5时执行了语句continue,
continue的作用就是跳过本次循环,继续下一次循环
在这段代码中也就是当i==5时,跳过这次循环,不再去执行后面的printf和i++;
打印完1 2 3 4之后并不会停下来,而是进入了死循环
总结: continue在while循环中的作用就是: continue是用于终止本次循环的,也就是本次循环中continue后边的代码不会再执行,而是直接跳转到while语句的判断部分,进行下一次循环的入口判断。
while语句中的break
还是看代码说话:
#include <stdio.h>
int main()
{
int i = 1;
while (i <= 10)
{
if (i == 5)
break;
printf("%d ", i);
i = i + 1;
}
return 0;
}
将原来的continue换为break之后又是怎样的呢?
程序输出1 2 3 4
分析:这里当等于5的时候,执行了if语句下面的break;这就直接跳出了这个while循环
总结: break在while循环中的作用:
其实在循环中只要遇到break,就停止后期的所有的循环,直接终止循环。 所以:while中的
break是用于永久终止循环的。
这里continue和break有下面的流程图帮助理解
案例分析
代码1:
#include <stdio.h>
int main()
{
int ch = 0;
while ((ch = getchar()) != EOF)
putchar(ch);
return 0;
}
说明:
getchar()从键盘获取一个字符,查文档会看到getchar函数的形式是int getchar( void );返回值是整型(实际就是返回所获取的字符的ASCII码值)
putchar()打印一个字符到屏幕上
EOF(end of file)文件的结束标志,本质值为-1
这里while语句后面的判断条件的意思就是从键盘获取一个字符,如果这个字符不是EOF,就执行下面的循环体(将获取的字符打印出来)。并且只要不是EOF,这个程序就可以一直读,一直打印。
运行结果:
注意:为什么这里输入EOF,循环并没有停下来呢?
这就是因为输入EOF相当于程序是分别读了EOF三个字母,然后分别打印了出来
要想循环条件为假,终止循环,应该要在键盘上按Ctrl+Z,getchar()就会从键盘上获取EOF
代码2:
#include <stdio.h>
int main()
{
int ch = 0;
char password[20];
printf("请输入密码\n");
scanf("%s", password);
printf("请确认密码(Y/N)\n");
ch = getchar();
if ('Y' == ch)
{
printf("确认成功\n");
}
else
{
printf("放弃确认\n");
}
return 0;
}
结果:
说明:
在按下Ctrl+F5之后我的操作: 在键盘输入123之后按了回车然后就成了上图所示的情况,我还没输入Y/S,就已经自动显示放弃确认了,这是什么情况呢?
要知道:scanf和getchar都是用来输入的,而我们从哪里输入呢? 当然就是从键盘输入,但scanf或者getchar并不是直接从键盘拿东西的。
输入函数和键盘之间有一个输入缓冲区,当我们在输入密码的时候,输入123,然后为了让密码能够输入进去,还会按回车键(相当于'\n'),然后密码就会被写进去。然后scanf会从输入缓冲区拿数据,它只会拿走123,而'\n'不会被拿走,接下来运行到getchar的时候,getchar发现输入缓冲区还有东西,然后getchar就会把'\n'拿走,所以我们就没有机会去往getchar里面输入数据
如下图所示:
解决方法:我们可以让scanf把密码从输入缓冲区拿走之后,想办法把这个'\n'也拿走,在getchar()执行之前就把'\n'去掉就可以了
在“请确认”之前再加一个getchar()取走'\n'
#include <stdio.h>
int main()
{
int ch = 0;
char password[20];
printf("请输入密码\n");
scanf("%s", password);
getchar();
printf("请确认密码(Y/N)\n");
ch = getchar();
if ('Y' == ch)
{
printf("确认成功\n");
}
else
{
printf("放弃确认\n");
}
return 0;
}
结果:
使用getchar()清空输入缓冲区
如果我要清空输入缓冲区,可借助getchar(),如下代码:
while (getchar() != '\n')
{
;
}
2.for语句
我们通过平时的练习其实也能发现:三个循环语句中,for用的最多,while次之,do…while最少
首先看以下while循环的代码
#include <stdio.h>
int main()
{
int a = 0; //初始化部分
while (a < 10) //判断部分
{
printf("%d ", a);
a += 2; //调整部分
}
return 0;
}
这段代码其实如果我们仔细看的话会发现这里面初始化部分,判断部分还有调整部分三部分离得比较远,不集中,如果代码比较长的话,就不好去判断与修改,而for循环恰好解决了这个问题
语法结构
for(表达式1;表达式2;表达式3)
循环语句;
- 表达式1为初始化部分,用于初始化循环变量的。
- 表达式2 表达式2为条件判断部分,用于判断循环时候终止。
- 表达式3 表达式3为调整部分,用于循环条件的调整。
将以上代码用for语句实现如下:
#include <stdio.h>
int main()
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", i);
}
return 0;
}
会发现for语句的书写和使用都更加方便
执行流程
for语句中的continue
上代码:
#include <stdio.h>
int main()
{
int i = 0;
for (i = 1; i <= 10; i++)
{
if (i == 5)
continue;
printf("%d ", i);
}
return 0;
}
可以先想想运行结果是什么?
分析:
在i==5时,执行continue,跳出本次循环,执行下一次循环,所以后面的printf没有被执行;在下一次循环时,i++,i变为了6,i<=10成立,然后if语句判断条件不成立,不执行continue,然后去执行了printf
运行结果:
这里会发现for语句里面的continue和for语句里面的continue是有一定的区别的,for语句里面的continue不会跳过i++,而while语句里面的continue可能会跳过i++(容易造成死循环)
for语句里面的break
#include <stdio.h>
int main()
{
int i = 0;
for (i = 1; i <= 10; i++)
{
if (i == 5)
break;
printf("%d ", i);
}
return 0;
}
分析:
这里在i==5时,执行break之后,不会再执行后面的printf,直接跳出整个for循环,结果应该为1 2 3 4
运行结果:
for语句的循环控制变量
建议:
- 不可在for 循环体内修改循环变量,防止 for 循环失去控制
- 建议for语句的循环控制变量的取值采用“前闭后开区间”写法,这个时候for语句后面的判断部分i < n,这里的n可以表示10次循环
int i = 0;
//前闭后开的写法
for(i=0; i<10; i++)
{}
//两边都是闭区间
for(i=0; i<=9; i++)
{}
一些for循环的变种
变种1
#include <stdio.h>
int main()
{
//变种1
for ( ; ; )
{
printf("hehe\n");
}
}
分析:
这段代码是直接死循环,为什么呢?
因为当for循环的判断部分省略掉的话,默认表示恒为真 (初始化部分,判断部分,调整部分建议不要轻易省略)
变种2
#include <stdio.h>
int main()
{
//变种2
int x, y;
for (x = 0, y = 0; x < 2 && y < 5; ++x, y++)
{
printf("hehe\n");
}
return 0;
}
变种二很容易理解,这里不再啰嗦
笔试题
//请问循环要循环多少次?
#include <stdio.h>
int main()
{
int i = 0;
int k = 0;
for (i = 0, k = 0; k = 0; i++, k++)
k++;
return 0;
}
分析:
for语句后面的判断部分k = 0,这里是将0赋给k,然后k的值是0,为假,所以循环直接就没有进行,循环次数为0
运行结果:
3. do…while循环
语法结构
do
循环语句;
while (表达式);
现在仍然是打印1--10,如下代码:
#include <stdio.h>
int main()
{
int i = 1;
do
{
printf("%d ", i);
i++;
} while (i <= 10);
return 0;
}
与for语句和while语句相比,do…while语句最明显的特点就是先执行后判断,先执行一次,然后再判断要不要进行下一次循环,所以循环体至少会被执行一次
执行流程
#include <stdio.h>
int main()
{
int i = 1;
do
{
printf("%d ", i);
i++;
} while (i <= 10);
return 0;
}
do…while语句里面的break
#include <stdio.h>
int main()
{
int i = 1;
do
{
if (5 == i)
{
break;
}
printf("%d ", i);
i++;
} while (i <= 10);
return 0;
}
这段代码的运行结果是什么呢?
分析:
这里的循环体里面包含了一个break,当i==5的时候执行break直接跳出了循环,所以结果应该是1 2 3 4
运行结果:
do…while语句里面的continue
#include <stdio.h>
int main()
{
int i = 1;
do
{
if (i == 5)
{
continue;
}
printf("%d ", i);
i++;
} while (i <= 10);
return 0;
}
分析:
这里在i==5的时候,执行continue,跳过本次循环,进行下一次循环,在后面的判断部分i<=10成立,继续进入循环,i一直都是5(continue跳过了调整部分i++),进入了死循环
运行结果:
四、goto语句
C语言中提供了可以随意滥用的 goto语句和标记跳转的标号
从理论上 goto语句是没有必要的,实践中没有goto语句也可以很容易的写出代码
但是某些场合下goto语句还是用得着的,最常见的用法就是终止程序在某些深度嵌套的结构的处理过程,例如一次跳出两层或多层循环。这种情况使用break是达不到目的的。它只能从最内层循环退出到上一层的循环。
请看以下代码:
#include <stdio.h>
int main()
{
flag:
printf("hello\n");
printf("goodbye\n");
goto flag;
}
分析:
C 语言里面goto其实就是想要到哪里去的意思,当代码运行到goto的时候,后面有个flag,拿代码就跳转到了flag的位置往下执行,我们会发现这其实也构成了一个循环
1.建议
在日常写代码的时候不建议使用goto语句,它虽然可以随意跳转,但是如果一个程序中goto语句过多,跳转来跳转去,就可能到最后连自己都分不清代码到底是什么样的逻辑,会让代码的逻辑变得混论,容易出问题
2.真正适合的场景
goto语句真正适合的场景应该是跳转出深层嵌套的循环
如下示例:
for (...)
for (...)
{
for (...)
{
if (disaster)
goto error;
}
}
…
error :
if (disaster)
// 处理错误情况
五、整型有序数组的查找
我们通过这样的案例来练习一下循环语句
现在有一个整型有序数组arr[10] = { 1,2,3,4,5,6,7,8,9,10 },现在要在这个数组里面找一个数字,返回这个元素的下表,我们该怎么找?
1.遍历法
分析:
我们可以将数组里面的每一个元素和我们想要找的元素进行比较,相同的话,就说明找到了,然后返回下标,没有相同的就没找到,没有这个元素
代码:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int k = 0;
int i = 0;
printf("请输入要查找的数字\n");
scanf("%d", &k);
for (i = 0; i < 10; i++)
{
if (k == arr[i])
{
printf("找到了,下标是%d\n", i);
break;
}
else
{
continue;
}
}
if (10 == i) //如果i==10,就说明已经把所有元素都找完了,还是没找到
{
printf("没找到\n");
}
}
运行结果:
最多需要查找n次
有序数组的查找,这样一个一个来比较就太慢了,所以我们可以通过下面的二分法来找
2.折半法/二分法
使用前提:必须是有序数组
因为是有序数组的查找,所以我们可以通过缩小范围的方式;来找,这样的话更快
比如在这个arr数组里面我要找7,而中间数字如果是5的话,5比7小,那么1--5之前pass掉,然后再在6--10里面,中间元素8,发现8比7大,所以8--10直接pass,然后再在6--7里面,中间元素6,6比7小,所以6pass,然后在7里面找,7的左右下标都是6,中间元素还是7,然后通过比较就找到了这个元素。这样的话大大的降低了复杂度(每找一次淘汰掉一半)
代码:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int k = 0;
int size = sizeof(arr) / sizeof(int);
int left = 0; //左下标
int right = size - 1;
int mid = 0;
printf("请输入要查找的数字\n");
scanf("%d", &k);
do
{
mid = (left + right) / 2;
if (arr[mid] < k)
{
left = mid + 1;
}
else if (arr[mid] > k)
{
right = mid - 1;
}
else
{
printf("找到了,下标是%d", mid);
break;
}
} while (left <= right);
if (left > right)
{
printf("没找到\n");
}
return 0;
}
最多查找Log2 N
--------------------------------------------------------------------------------
-------------------------分支与循环语句完结-----------------------------
关于C语言,每个知识点后面都会单独写博客更加详细的介绍
欢迎大家关注!!!
一起学习交流 !!!
让我们将编程进行到底!!!
--------------整理不易,请三连支持-----------------