C语言详解:分支循环结构
分支循环结构
结束了初识C语言,现在我们再从头细细的学起。首先映入眼帘的是三大结构,分别是顺序结构,选择结构,循环结构。生活中的事情大大小小基本上都可以通过这三种结构描述。
C语言是一门结构化的编程语言。顺序结构不必多言,选择结构有if
语句,switch
语句,循环结构有while
循环,for
循环以及do...while
循环语句,还会介绍到goto
语句。
语句
以分号;
结尾的,用分号;
隔开的就是一条语句。例如printf("hehe\n");
,这便是一条语句。甚至还有只有一个分号;
的语句称为空语句。
printf("hehe\n");
1+2;
;//空语句
1 选择结构
选择结构对应分支语句或称选择语句。比如这么一个简单的例子就可以用分支语句描述。
1.1 if 语句
语法结构
if语句的语法分为单分支和多分支两种逻辑结构。
//1.单分支
if (表达式)
{语句;
}
else
{语句;
}//2. 多分支
if (表达式)
{语句;
}
else if (表达式)
{语句;
}
else if (表达式)
{语句;
}
...
else
{语句;
}
条件表达式若为真,就进入语句,反正为假,则不执行。
示例
int main()
{int age = 20;//多分支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");elseprintf("快没了\n");return 0;
}
当然如果想一个if语句控制多条语句则需要使用代码块。即用一个大括号将所以语句括在一起。这样还有一个明显的好处就是可以提高代码的可读性,一般实际工作中都会采用这种形式。如:
int main()
{int age = 10;if (age >= 18){printf("成年了\n");printf("可以谈恋爱了\n");}return 0;
}
问题
下面是关于if语句的一些常见的问题及一些特例。
- 条件表达式错误
//18 <= age < 30
int main()
{int age = 50;if (18 <= age < 30)printf("青年\n");return 0;
}
初学者很容易写出这样的代码,问题就出现在判断条件上,编程语言里18 <= age < 30的意义是一个表达式。
先看左边的18 <= age 无论其是真是假,真则为1,假则为0。1或0都是要小于30的,故整个表达式为真,所以无论怎样都会执行if语句。
- 悬空
else
悬空else
是一个经典的问题。
//悬空else
int main()
{int a = 0;int b = 2;if (a == 1)if (b == 2)printf("hehe\n");elseprintf("haha\n");//else与其上最近的一个if匹配return 0;
}
该代码的else部分虽然是和第一个if对齐,但是else总是与其上最近的一个if搭配,不受代码对齐的影响。所以该else和上一个if都是嵌套在第一个if语句里的。
故修改后应为下图,显著的提示了代码的可读性。
int main()
{int a = 0;int b = 2;if (a == 1){if (b == 2)printf("hehe\n");elseprintf("haha\n");//else与其上最近的一个if匹配}return 0;
}
- 判断表达式误写成赋值表达式
还有就是错把比较相等的表达式写出赋值表达式。这也是一个初学者经常犯的,不起眼的却又很难找且编译器不报错的错误。
int main()
{int a = 0; if (a = 5)printf("hehe\n");return 0;
}
在上面的代码中,错把a==5写成了a=5。但是逻辑上又没有什么问题,a=5是赋值表达式,其值为5为真,所以判断条件失效,无论如何都执行if语句。
//建议改成
int main()
{int a = 0;if (5 == a)printf("hehe\n");return 0;
}
把a == 5
改成5 == a
,这样一来如果再写成赋值表达式,就出现了语法错误常量是不可赋值的,编译器就会报警。
- 函数返回后在函数内写代码
if (condition)
return x;
return y;
该代码的含义为如果条件成立,则返回x,否则返回y。
void test()
{if (1){printf("hehe\n");return 0;}//函数返回值后,后面的代码不执行printf("haha\n");
}int main()
{test();return 0;
}
从上面的代码可以看出,函数返回值后,下面的代码将不再执行。所以函数不可能又返回x又返回y。
练习
- 输出0到100之间的奇数。
int main()
{int i = 0;while (i <= 100){if (i % 2 == 0)printf("%d ", i);i++;}for (i = 0; i < 100; i++){if (i % 2 == 1){printf("%d ", i);}}return 0;
}
1.2 switch 语句
语法结构
switch (整型表达式)
{语句项;
}
之所以会存在switch语句,是因为在某些情况下使用if…else语句会显得过于繁琐。
int main()
{int day = 0;scanf("%d", &day);switch (day)//整型表达式{case 1://整型常量表达式{printf("星期一\n");printf("美好的一天,从上班开始5555~");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;
}
注意
首先switch后面的表达式必须是整型表达式,即其后变量必须为整型。
其中case语句项里也必须是整型常量表达式,既是整型也是常量缺一不可。
从上述代码中我们也可以看出,case语句项若想控制多条语句,仍需使用代码块。
上述代码的逻辑里,必须在每种情况下都要有break跳出去,不然的话代码就会一直向下走。
case是代码入口,break是代码出口。
但是若有时我们不需要分的如此清楚,比如1到5我们定为工作日,6到7我们定为休息日。那么我们的代码就可以这么改。
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;default:printf("输入数据有误,请重新输入\n");break;}return 0;
}
但从上述代码中我们还可以得到以下信息。
- 虽然多个case对应一种结果,但是最后仍需加上break;
- default子句:适配所有不符合case情况的情况。即当所有的case标签都不满足时,便会来到default的情况。
default子句末尾最好也加上break;这样后续如果再加入其他情况时,不至于代码错误。
- 另外,所以case语句不存在顺序问题,即无论顺序怎么排,都无所谓。
总结:
if
语句里的break
&continue
都是为了控制外面的循环等语句。switch
语句里是break
结束switch
语句,代码不往下走,switch
语句里没有continue
。
2 循环结构
2.1 while 循环
//while语法结构
while(表达式)
{循环语句;
}
while
语句的执行流程如下:
条件为真,则进入循环反之则退出循环。这并不难理解。
break&continue
但是图中break
,和continue
是什么意思呢?break
是否和在switch
里是一个用法呢?
int main()
{int i = 0;while (i <= 10){if (i == 6)break;printf("%d ", i);i++;}return 0;
}
如上述代码,当i=6时,break后则终止了循环,所以输出结果为0 1 2 3 4 5 。
int main()
{ int i = 0;while (i <= 10){if (i == 6)continue;printf("%d ", i);i++;}return 0;
}
但是当把
break
换成continue
则会发生什么呢?细心想一想就会发现,该程序陷入了死循环中。
先是输出了0 1 2 3 4 5 ,但当i=6时,continue
跳出了本次循环,直接进入下一次循环,但此时i并没有得到i++
,所以还一直处于i=6的状态,故一直在continue
,一直死循环。
int main()
{int i = 0;while (i <= 10){i++;if (i == 6)continue;printf("%d ", i);//1 2 3 4 5 7 8 9 10 11}return 0;
}
把
i++
,放到了前面这样的话,我们的i便有了++
的机会。
总结:
循环中只要遇到
break
,就停止后期所有循环,直接终止循环。所以while
中的break
是用于永久终止循环的。continue
用于终止本次循环,也就是本次循环中continue
后边的代码不会再执行,结束本次循环直接跳转到while语句的判断部分,进行下一次循环的入口判断。
特例
先看下面代码是什么意思。
int main()
{int ch = 0;while ((ch = getchar()) != EOF)putchar(ch);return 0;
}
这里我们先简单的介绍两个函数getchar
和putchar
。
通俗的说,
getchar
就是从键盘获取字符,getchar
就是把字符打印到屏幕。EOF
即end of file是个文件结束标志。判断条件中的ch =getchar
,同样返回的也是字符的ACSII码值。这也是为什么,ch用的是整型int
定义的原因。
由图可知,getchar
和scanf
两个常见的输入函数,虽然我们都说是从键盘上读取字符,但其实是先从键盘再到输入缓冲区。输入缓冲区是一块特殊的内存空间。getchar
和scanf
是从输入缓冲区里读取的字符。
二者读取的方式有所不同,
scanf
读取到空格就停下不读了,getchar
一次只读取一个字符。但又有相同之处,如果输入缓冲区没东西,那么就等待我们从键盘上输入。如果输入缓冲区有东西,就直接读取。
应用到实例就是这样的一个输入密码的程序。
int main()
{int ch = 0;char password[20] = { 0 };printf("请输入密码>:");scanf("%s", password);printf("请确认密码(Y/N):>");//清理\n//getchar();//只能清理一个字符while ((ch = getchar()) != '\n'){;}ch = getchar();if (ch == 'Y')printf("确认成功\n");elseprintf("确认失败\n");return 0;
}
第二个特例就是这样的一个程序
//只打印数字字符
int main()
{int ch = 0;while ((ch = getchar()) != EOF){if (ch < '0' || ch > '9')//排除数字字符以外的所有字符continue;putchar(ch);}return 0;
}
该程序的特点就是排除数字字符以外的所有字符。
2.2 for 循环
既然已经有了while循环,那我们为什么还要学for循环呢?
for循环才是我们日后使用最多的循环语句。先看其语法结构。
for (表达式1, 表达式2, 表达式3)循环语句;
- 表达式1为初始化部分,用于初始化循环变量。
- 表达式2为条件判断部分,用于判断什么时候终止循环。
- 表达式3为调整部分,用于循环条件的调整。
循环最重要的三个部分被集中在一起,与while循环形成了鲜明的对比。
int i = 1;//初始化部分
//...
while (i <= 10)//判断部分
{//...printf("%d ", i);//...i++;//调整部分
}
当我们的while循环体写的够多够大时,其弊病就显现出来了,三个部分分离,不好找,修改时容易引起错误。
int main()
{int i = 0;for (i = 1; i <= 10; i++){printf("%d ", i);}return 0;
}
这是一个for循环的例子,很清晰,很简单。
break&continue
int main()
{int i = 0;for (i = 1; i <= 10; i++){if (i == 5)break;printf("%d ",i);//1 2 3 4 }return 0;
}
这是for循环里·break
的作用,显而易见,和其他循环甚至和分支语句里的作用都一样。是直接终止循环,或者说是跳出语句。
int main()
{int i = 0;for (i = 1; i <= 10; i++){if (i == 5)continue;printf("%d ", i);//1 2 3 4 6 7 8 9 10 }return 0;
}
而如果将这里的break改成continue会怎么样呢?显然跳过了i=5的情况,直接跳到了for循环的调整部分,调整循环变量,不容易造成死循环。
提示:为加以区别,可记成continue
跳过循环体下面的代码(只有for循环的调整部分在循环体上面所以不受影响,其他循环都不行)
建议
- 不可在for循环体内修改循环变量,防止for循环失去控制。
例如还是这条小白经常忽略的语病,判断表达式写出赋值表达式。
int main()
{int i = 0;for (i = 1; i <= 10; i++){if (i = 5)//循环体内修改循环变量printf("%d ",i);}return 0;
}
这还是相当于在循环体内改变了循环变量,这便造成了5555…的死循环。
int main()
{int i = 0;for (i = 1; i <= 10; i++){ printf("%d ", i);i++;}return 0;
}
尽量不要这样,可以在循环调整部分修改为
i+=2
。不然的话,代码写多了,就把下面的i++
给忘了。
- 建议for语句的循环控制变量的取值采用"左闭右开"写法。
int main()
{int i = 0;int arr[20] = { 0 };//0~9for (i = 0; i < 10; i++)//左闭右开{scanf("%d", &arr[i]);}for (i = 0; i < 10; i++)//左闭右开{printf("%d ", arr[i]);}return 0;
}
例如这样很经典的用for循环遍历数组元素来赋值和输出的用法。这里循环变量i
就拥有了更多的意义,比如i
从0开始,表示数组下标从0开始,i<10
表示i
循环10次。也不是说必须要左开右闭,根据事实情况来看,目的就是使循环变量拥有更多的意义。
变种
- 省略
考试也容易考,但是一般真正写代码时不会这样。
int i=0;for (;;){printf("%d ", i);}
若把三个循环部分都省了,可不可行?初始化,判断,调整部分都可省略。
- 初始化部分省略,则在创建变量时就已经初始化。
- 判断部分省略,意味着判断条件恒为真。
- 调整部分省略,那就不调整呗。
int i=0;
int j=0;
for ( ; i < 10; i++)
{for ( ; j < 10; j++){printf("hehe\n");}
}
答案是屏幕上打印了10个hehe。
其实在第一遍外循环结束之后,内循环就不再循环了,因为内循环初始化条件的缺失,之后的
j
始终等于10,故一直不进内循环,所以只有i=1
时循环一遍。
- 多循环变量的情况
int main()
{int x = 0;int y = 0;for (x = 0, y = 0; x < 5 && y < 7; x++, y++){printf("hehe\n");}return 0;
}
该种情况也是可以的,该怎么循环和单循环变量一样。现在我们来看一下一道笔试题,既然出出来考我们,那么一定有坑,还请各位细细思量一下,小提示:和我前面介绍的是一种的经典语病。
请问下面代码循环几遍?
int main()
{int i = 0;int k = 0;for (i = 0, k = 0; k = 0; i++, k++){k++;}return 0;
}
当然是一遍都不循环啦。判断条件写成赋值表达式,k=0恒为假。
2.3 do…while 循环
先看它的语法结构。
do
{循环语句;
}while(表达式)
他的特点显而易见,是先执行再判断。
所以就算条件不满足,也至少执行一次,使用的场景有限,所以不是经常使用。
break&continue
再来看它的break和continue的用法。
int main()
{int i = 1;do{if (i == 5)break;printf("hehe\n");i++;} while (i < 10);return 0;
}
和其他循环并无二致。
int main()
{int i = 1;do{if (i == 5)continue;printf("hehe\n");i++;} while (i < 10);return 0;
}
//死循环
这里也是和while循环一样,因跳过了i++
这样的进步条件。所以造成了死循环。
循环语句里break都是提前终止循环。continue都是跳过其后部分的代码,重新开始下一次的循环。
练习题
- 计算n的阶乘。
//封装阶乘函数
int factorial()
{int n = 0;int i = 1;int pro = 1;scanf("%d", &n);for (i = 1; i <= n; i++){pro *= i;}return pro;
}
- 计算1!+2!+3!..+10!
//封装阶乘求和函数
int sum_on_factorial()
{int i = 0;int n = 0;int pro = 1;int sum = 0;for (n = 1; n <= 10; n++){pro = 1;for (i = 1; i <= n; i++){pro *= i;}sum += pro;}return sum;
}
这样还有点复杂,还可以优化。
//修改后
int sum_on_factorial2()
{int n = 0;int pro = 1;int sum = 0;for (n = 1; n <= 10; n++){//这里的pro就不用初始化了pro *= n;sum += pro;}return sum;
}
- 在一个有序数组中查找具体的某个数字n。
编写
int binsearch(int x, int v, int n);
功能︰在v[0]<=v[1]<=v[2]<=…<=v[n-1]的数组中查找x.
//封装有序数组查找元素函数
void findkey()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int key = 7;//求数组长度int sz = sizeof(arr) / sizeof(arr[0]);//遍历数组int i = 0;for (i = 0; i < sz; i++){if (arr[i] == key){printf("找到了,下标是%d\n", i);break;}}if (i == sz){printf("找不到\n");}}
上面是最基本的遍历数组的方法。当然还有更好的方法:二分查找。数组越大,效率越高。
//封装有序数组查找元素函数
void findkey()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int key = 7;//求数组长度int sz = sizeof(arr) / sizeof(arr[0]);//二分查找int left = 0;int right = sz - 1;while (left <= right){int mid = (left + right) / 2;if (arr[mid] > key){right = mid - 1;}else if (arr[mid] < key){left = mid + 1;}else{printf("找到了,下标为%d\n", mid);break;}}if (left > right)printf("没找到\n");
}
- 编写代码,演示多个字符从两端移动,向中间汇聚。
//演示一行代码从两端移动,向中间汇聚
#include <string.h>
#include <windows.h>
#include <stdlib.h>
int string_move()
{char arr1[] = "happy birthday to you!!!";char arr2[] = "########################";int len = strlen(arr1);int left = 0;int right = len - 1;while (left <= right){arr2[left] = arr1[left];arr2[right] = arr1[right];printf("%s\n", arr2);Sleep(500);//1000mssystem("cls");left++;right--;}printf("%s\n", arr2);return 0;
}
代码运行过程不好用语言描述,感兴趣的朋友们可以自己试试。
- 编写代码实现,模拟用户登录情景
并且只能登录三次。(只允许输入三次密码,如果密码正确则提示登录成,如果三次均输入错误,则退出程序。
//模拟用户登录情景
#include <string.h>
int followsignup()
{int i = 0;char password[20] = { 0 };//假设密码为abcdeffor (i = 0; i < 3; i++){printf("请输入密码:>\n");scanf("%s", password);if (strcmp(password, "abcdef") == 0)//字符串比较函数{printf("密码正确\n");break;}else{printf("密码输入错误,请重新输入\n");}}if (i == 3){printf("三次输入错误,退出系统\n");}else{printf("登录成功\n");}return 0;
}
2.4 goto
语句
C语言中提供了可以随意滥用的goto
语句和标记跳转的标号。如:
int main()
{again://...goto again;return 0;
}
当然,这里的again
只是一个标记名字而已,像函数名一样随便取,a也好b也罢,只是为了更有意义罢了。
从理论上
goto
语句是没有必要的,一般来说用goto
语句的地方也可以用循环代替。其常见用法就是使程序在各种深层嵌套的代码中一次性跳出来,
break
一般只能跳出一次的循环。
实例
下面是使用goto
语句的一个很有趣的例子︰关机程序。
//关机程序
#include <windows.h>
int Shutdown()
{char input[20] = { 0 };system("shutdown -s -t 60");printf("你的电脑将于60s内关机,若输入我是猪,则取消关机\n");
again:scanf("%s", input);if (strcmp(input, "我是猪") == 0){system("shutdown -a");}else{goto again;}return 0;
}
这里我就不过多介绍了,各位亲自去了解下还是很有趣的,笔者当时是笑着学完的。
C语言详解:分支循环结构相关推荐
- C语言详解系列——循环语句详解(1)while语句的语法结构
文章目录 while语句 break continue while语句 之前的学习中我们了解到了if语句的用法,这个语句只会执行一次,但在我们的生活当中有许多事情是需要重复去做的,那我们应该怎么实现呢 ...
- C语言详解系列——循环语句详解(3)do while语句的语法结构
文章目录 do while语句 do while语句中的break和continue break continue do while语句与while语句的不同点 简单练习 do while语句 之前的 ...
- C语言详解系列——循环语句详解(2)for语句的语法结构
文章目录 for语句 break与continue 关于if语句的建议 关于for循环的变种写法 一道笔试题 for语句 在之前的学习中我们已经了解到了循环结构中while语句的用法,那么为什么我们还 ...
- C语言详解系列——分支语句详解if、switch
文章目录 什么是语句 if语句 悬空else switch语句 break的使用 default 的使用 在之前的分享中,我们了解到了c语言是结构化的程序设计语言.分为顺序结构,选择结构,循环结构三大 ...
- c语言霍夫曼函数,使用C语言详解霍夫曼树数据结构
1.基本概念 a.路径和路径长度 若在一棵树中存在着一个结点序列 k1,k2,--,kj, 使得 ki是ki+1 的双亲(1<=i 从 k1 到 kj 所经过的分支数称为这两点之间的路径长度,它 ...
- Drools 规则语言详解(上)
http://www.blogjava.net/guangnian0412/archive/2006/06/09/51574.html http://www.blogjava.net/guangnia ...
- 有向图邻接矩阵c语言编程,邻接矩阵有向图(一)之 C语言详解
本章介绍邻接矩阵有向图.在"图的理论基础"中已经对图进行了理论介绍,这里就不再对图的概念进行重复说明了.和以往一样,本文会先给出C语言的实现:后续再分别给出C++和Java版本的实 ...
- 克鲁斯卡尔算法c语言,Kruskal算法(一)之 C语言详解
最小生成树 在含有n个顶点的连通图中选择n-1条边,构成一棵极小连通子图,并使该连通子图中n-1条边上权值之和达到最小,则称其为连通网的最小生成树. 例如,对于如上图G4所示的连通网可以有多棵权值总和 ...
- c++指针详解_c语言详解sizeof
一.sizeof的概念 sizeof是C语言的一种单目操作符,如C语言的其他操作符++.--等. 它并不是函数. sizeof操作符以字节形式给出了其操作数的存储大小. 操作数可以是一个表达式或括在括 ...
最新文章
- 华师大计算机网络中心,华东师范大学 单点登录共享校际资源
- 使用 Java 开发 OpenCV 应用
- 在Ubuntu上安装openResty #1
- 开源的库RestSharp轻松消费Restful Service
- Web安全——服务器端请求伪造(SSRF)
- 《嵌入式linux应用程序开发标准教程》笔记——7.进程控制开发
- Groovy 设计模式 -- 抽象工厂 模式
- [bzoj1008] [HNOI2008]越狱
- busybox订制小型linux(二)
- php重写curl_setopt函数,PHP curl_share_setopt函数
- 20200227:存在重复元素Ⅱ(leetcode219)
- C++11 Unicode 支持
- 磁力搜索 v2.3.5.0 for Android 免费无广告版
- mysql查看被锁住的表
- 如何用C++写一个简单的小游戏?
- 算法提高 金明的预算方案
- Games101-课程15笔记
- 1677 treecnt(贡献)
- this.$router.push方法,父子如何传值和接收值
- 软件项目管理的平衡原则和高效原则