分支与循环
一、简介(何为语句)
C语句可分为以下五类:
- 表达式语句
- 函数调用语句
- 控制语句
- 复合语句
- 空语句
本文章介绍的是控制语句。控制语句用于控制程序的执行流程,以实现程序的各种结构方式
(C语言支持三种结构:顺序结构、选择结构、循环结构),它们由特定的语句定义符组成,
C语言有九种控制语句。可分成以下三类:
条件判断语句也叫分支语句:if语句、switch语句;
循环执行语句:do while语句、while语句、for语句;
转向语句:break语句、goto语句、continue语句、return语句。
二、选择语句
1.if语句
1)语法结构
if(表达式1)
语句1;
else if(表达式2)
语句2;
else
语句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
{
printf("老年\n");
}
}
这里面也有一些需要注意的地方
例如:
1…else后面没有括号,也不需要再加任何条件了。
2.还有就是与循环这个知识点无关的错误,那就是在判断部分如果写成(30<=age<50)这种也是错误的。这个地方的错误就是因为结合性方面的部分问题,这个错误部分会先判断左边的(30<=age)然后根据真假变成0\1再去和右边的50比较。(注:在C语言中如何表示真假?0表示假,非0表示真。)
3.如果在if后面不加任何符号,那么只能跟一个语句。例如这里如果年龄小于18时,我想让它多打印一个不能早恋,可是不管你输入的是否符合条件,它都会打印“还不能早恋”这句话。
2)悬空else
例如:
这个代码本意是想表达18岁以内并且小于5岁是幼年,如果>=18岁则是成年,而从结果可以明显看出并非如此。
造成结果的原因正如标题所示,是因为悬空的else(注;else总是和最近且没有匹配的if配对)。
改正后:
#include <stdio.h>
int main()
{
int age = 0;
scanf("%d", &age);
if (age < 18)
{
if (age < 5)
printf("幼年");
}
else
printf("成年");
}
由此可见适当的使用{}可以使代码的逻辑更加清楚,代码风格很重要。
3)if书写形式的对比
有的时候同一个实现功能的代码用不同形式表达出来给人的感觉也是截然不同。
就例如下面俩:
代码1:
if (a) {
return x;
}
return y;
代码2:
if(a)
{
return x;
}
else
{
return y;
}
这俩经过分析可以看出所达到的效果是一样的。但是在理解时候,明显可以看出代码2看着更容易理解一些。
2.switch语句
1) 语法结构
switch语句也是一种分支语句,常常用于多分支的情况。
switch(整型表达式)
{
case 整形常量表达式:
语句;
case 整形常量表达式:
语句;
case 整形常量表达式:
语句;
}
2)在switch语句中的 break
例子:
在case中只有普通的语句的时候会发现,switch语句是实现了选择并且星期五可以正常打印,但是后面的星期六和星期天也会打印,这就是因为没有用上break;接下来给大家讲一下这个break在switch语句中的作用。
把上面的代码改成:
在改完之后也是不难看出可以完全实现这个功能,break在switch语句中的作用就是不再执行后面的语句,有一种截断的作用(break语句的实际效果是把语句列表划分为不同的分支部分。)
3)default语句
如果表达的值与所有的case标签的值都不匹配怎么办?结构里面所有的语句都被跳过。程序并不会终止,也不会报错(在C中并不认为是个错误)。
如果你并不想忽略不匹配所有标签的表达式的值时,便可以加一个default子句。
当 switch 表达式的值不匹配所有 case 的值时,这个 default 子句里面的语句就会执行。因此每个switch语句中只能出现一条default子句。但是它可以出现在语句列表的任何位置,可以是任意一个case前后。
推荐编程习惯:在每个 switch 语句中都放一条default子句是个好习惯,也可以在default后边再加一个break 。
三、循环语句
1.简介
以if语句来讲,当条件满足的情况下,if语句后的语句执行,否则不执行,但是这个语句只会执行一次。而很多时候只i进行一次是无法实现的,所以就需要循环语句来执行
2.while循环
1)语法结构
while(表达式)
循环语句;
生活中常见例子实现(打印从1到10):
#include <stdio.h>
int main()
{
int i = 1;
while(i<=10)
{
printf("%d ", i);
i = i+1;
}
return 0;
}
2)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;
}
结果如图所示:
由此可以看出break在while循环中也有截断的作用,只要遇到break,就停止后期的所有的循环,直接终止循环
break作用: 用于永久终止循环的。
3)while里的contiune
#include <stdio.h>
int main()
{
int i = 1;
while (i <= 10)
{
if (i == 5)
continue;
printf("%d ", i);
i = i + 1;
}
return 0;
}
这里看似结果和前面break语句结果相同,可意义上却又很大的不同,4后面其实是有光标的并且在不断的闪烁,说明这个循环一直没有结束,而是陷入了死循环。(代码流程:i<5之前流程都正常进行,在i=5进入循环时,if判断为真,执行continue,然后continue会让此处循环后面的所有代码都不执行,再然后i再次进入循环还是5再执行continue所以会导致死循环)
continue作用:是用于终止本次循环的,也就是本次循环中continue后边的代码不会再执行,而是直接跳转到while语句的判断部分。进行下一次循环的入口判断。
continue的进阶应用:
先理解这个代码:
#include <stdio.h>
int main()
{
int ch = 0;
while ((ch = getchar()) != EOF)
putchar(ch);
return 0;
}
这个while循环用到的很是巧妙,其中还利用到了getchar() (getchar先读取一个字符放到ch里面去,如果这个字符不等于EOF,就进入循环,打印这个字符。当getchar读到文件末尾或者结束时,它会返回一个EOF,此时结束循环。)
注:getchar一次只能读取一个字符
#include <stdio.h>
int main()
{
char ch = '\0';
while ((ch = getchar()) != EOF)
{
if (ch < '0'|| ch > '9')
continue;
putchar(ch);
}
return 0;
}
上面这个代码经过观察可以看出它的功能是:只打印数字字符,跳过其他字符。
3.for循环
1)语法结构
for(表达式1; 表达式2; 表达式3)
循环语句;
注意:
表达式1为初始化部分,用于初始化循环变量的。
表达式2为条件判断部分,用于判断循环时候终止。
表达式3为调整部分,用于循环条件的调整。
例子:在屏幕上打印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;
}
2)while循环 和 for循环的区别
可以看到在讲解while循环和for循环时讲的是同一个功能(打印从1到10)的实现,但两者还是有很大的区别
int i = 0;
i=1;//初始化部分
while(i<=10)//判断部分
{
printf("hehe\n");
i = i+1;//调整部分
}
//实现相同的功能,使用for
for(i=1; i<=10; i++)
{
printf("hehe\n");
}
在很多时候for循环要比while循环看着更加简洁。(在while循环中依然存在循环的三个必须条件,但是由于风格的问题使得三个部分很可能偏离较远,这样查找修改就不够集中和方便。所以,for循环风格更好、使用频率更高。
注:while循环更适合不知道循环次数功能的实现,for循环更适合知道循环次数功能的实现。
3)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;
}
可以很明显看到结果如下:
for循环整个如下图所示:
那么不知道各位有没有好奇过break终止循环是只终止后面的执行语句,然后让整个程序调整部分执行完,然后才终止循环 还是 当即了断直接停止后续所有工作?
为了测试,我又改了下上面的代码:
在这个地方就可以很明显看出这个结果是符合第二种猜想的(当即了断直接停止后续所有工作)
4)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;
}
结果:
对于continue我在思考时也猜想它到底符合上面两种猜想的哪一种,从程序结果来看,与break不同,continue符合第一个猜想。
5) for语句的循环控制变量
-
for 循环体内的循环变量不去修改,防止 for 循环失去控制。(以这个为例for(i=0; i<10; i++) 循环变量就是那个i)
-
建议for语句的循环控制变量的取值采用“前闭后开区间”写法。
int i = 0;
//前闭后开的写法
for(i=0; i<10; i++)
{}
//两边都是闭区间
for(i=0; i<=9; i++)
{}
6)for循环的变种
例1:
for循环中的初始化部分,判断部分,调整部分是可以省略,但是不建议初学时省略,容易导致问题。
#include <stdio.h>
int main()
{
for (;;)
{
printf("hehe\n");
}
}
结果:一直重复的打印“hehe” 。这是因为没有了判断部分,程序一直认为判断条件为真,所以循环一直执行。
例2:
for循环的镶嵌使用
#include <stdio.h>
int main()
{
int i = 0;
int j = 0;
int a = 0;
//这里打印多少个hehe?
for (i = 0; i < 10; i++)
{
for (j = 0; j < 10; j++)
{
a += 1;
printf("%d ", a); //为了更好的计数
printf("hehe\n");
}
}
}
结果:打印了100次hehe
解析 :外面的for循环判断为真,然后进入第二个for循环,第二个for循环执行10次,然后又跳到外面的for循环,外面的for循环判断为真再次进入第二个for循环,第二个for循环把循环变量j = 0 然后又执行了10次,如此反复就执行了100次。
例3:
如果将上面把上面代码改成下面这个,省略掉初始化部分,这里打印多少个hehe?
#include <stdio.h>
int main()
{
int i = 0;
int j = 0;
for (; i < 10; i++)
{
for (; j < 10; j++)
{
printf("hehe\n");
}
}
}
结果:
例4:
使用多个变量控制循环
#include <stdio.h>
int main()
{
int x, y;
for (x = 0, y = 0; x < 2 && y < 5; ++x, y++)
{
printf("hehe\n");
}
return 0;
}
结果:
( ps:用变量空间是左值,用数值是右值)
例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次
原因:循环会根据语句来判断真假,真为非0,假为0,而k=0,直接让判断条件为0是假,所以一次都不会执行。
4.do…while循环
1)语法结构
do
{
循环语句;
}
while(表达式);
2)do语句的特点
循环至少执行一次,使用的场景有限,所以不是经常使用。
例1
#include <stdio.h>
int main()
{
int i = 1;
do
{
printf("%d ", i);
i = i + 1;
} while (i <= 10);
return 0;
}
运行结果:
3)do…while循环里面的break
例1
#include <stdio.h>
int main()
{
int i = 1;
do
{
if (5 == i)
break;
printf("%d ", i);
i = i + 1;
} while (i <= 10);
return 0;
}
运行结果:
可以看出与 for循环 / while循环 一样都是终止循环。
3)do…while循环里面的continue
例1
#include <stdio.h>
int main()
{
int i = 1;
do
{
if (5 == i)
continue;
printf("%d ", i);
i = i + 1;
} while (i <= 10);
return 0;
}
运行结果:
ps:这里的continue会导致循环先打印1 2 3 4 然后就会进入不能打印5的死循环。
四、练习
1.求n的阶乘
#include<stdio.h>
int main()
{
int n = 0;
int pro = 1;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
pro*= i;
}
printf("%d", pro);
}
运行结果:
这个练习还是很简单的。
2. 计算 1!+2!+3!+……+10!
初始版:
#include<stdio.h>
int main()
{
int i1 = 0;
int pro = 1;
int sum = 0;
for (i1 = 1; i1 <= 10; i1++)
{
pro = 1;
for (int i2 = 1; i2 <= i1; i2++)
{
pro *= i2;
}
sum += pro;
}
printf("%d", sum);
}
运行结果:
优化版:
#include<stdio.h>
int main()
{
int i1 = 0;
int pro = 1;
int sum = 0;
for (i1 = 1; i1 <= 10; i1++)
{
//简化了很多东西
pro *= i1;
sum += pro;
}
printf("%d", sum);
}
3. 在有序数组中查找具体的某个数字n(二分查找)
#include<stdio.h>
int main()
{
char arr1[] = { 1,2,3,4,5,6,7,8,9 };
int a = 0;
int sz = sizeof(arr1) / sizeof(arr1[0]);
int left = 0;
int right = sz - 1; //这里right是用作下标的
int mem = (left + right) / 2;
printf("输入你想找的数:");
scanf("%d", &a);
while (left <= right)
{
int mem = (left + right) / 2;
if (a > arr1[mem])
{
left = mem;
}
else if (a < arr1[mem])
{
right = mem;
}
else
{
printf("下标为%d的数组元素就是你要找的", mem);
break; //让程序停止,不一直重复打印
}
}
}
运行结果:
ps:非二分查找,一个一个找有2的N次方可能性
而二分查找只需要找log2 N
4. 编写代码,演示多个字符从两端移动,向中间汇聚。
#include<stdio.h>
#include<stdlib.h>
int main()
{
char arr1[] ="xxxxxxxxxxx";
char arr2[] ="hello world";
int sz = strlen(arr1);
int left = 0;
int right = sz - 1;
while (left <= right)
{
arr1[left] = arr2[left];
arr1[right] = arr2[right];
printf("%s\n", arr1);
Sleep(1000); //作用:每1000毫秒进行下一次打印
left++;
right--;
}
return 0;
}
运行结果:
5. 编写代码实现,模拟用户登录情景,并且只能登录三次。(只允许输入三次密码,如果密码正确则提示登录成,如果三次均输入错误,则退出程序。
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "123456";
char arr2[20] = { 0 };
int a = 0;
for (int i = 0; i < 3; i++)
{
scanf("%s", arr2);
if (strcmp(arr1, arr2) == 0)
{
printf("输入密码正确");
break;
}
else
{
printf("输入密码错误,您还有%d次机会\n", 2-i);
}
}
}
运行结果:
6.猜数字游戏实现
#include<stdio.h>
#include <stdlib.h> /* srand, rand */
#include <time.h> /* time */
void menu()
{
printf("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
printf("xxxxx menu xxxxx\n");
printf("xxxxx 1.paly xxxxx\n");
printf("xxxxx 1.exit xxxxx\n");
printf("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
}
void play()
{
srand((unsigned int) time(NULL));
int n = 0;
printf("游戏开始,请输入你认为的数(1~100)");
int randdom = (rand()%100 + 1) ;
while (1)
{
printf(":>");
scanf("%d", &n);
if (n == randdom)
{
printf("恭喜你猜对了\n");
break;
}
else if (n > randdom)
{
printf("猜大了\n");
}
else
{
printf("猜小了\n");
}
}
}
void end() //这个还不能用exit 与库函数某个函数重合定义
{
printf("欢迎您下次使用本游戏,再见!"); //break不能在函数里面 影响主函数里面的循环
}
int main()
{
int a = 0;
int b = 0;
while (1)
{
menu();
printf("请输入功能>");
scanf("%d", &a);
if (a == 1)
{
play();
}
else if (a == 0)
{
end();
break;
}
else
{
printf("输入了无效数字,请重新输入\n");
}
}
return 0;
}
运行结果:
五、goto语句
1.介绍
C语言中提供了可以随意滥用的 goto语句和标记跳转的标号。从理论上 goto语句是没有必要的,实践中没有goto语句也可以很容易的写出代码。但是某些场合下goto语句还是用得着的,最常见的用法就是终止程序在某些深度嵌套的结构的处理过程。
(ps:可以向上跳转,也可以向下跳转,他的跳转不可跨函数)
例如:一次跳出两层或多层循环。
多层循环这种情况使用break是达不到目的的。它只能从最内层循环退出到上一层的循环。
2.语法练习1
#include<stdio.h>
int main()
{
again:
printf("haha\n");
goto again;
}
运行结果:(死循环的打印haha)
3.关机程序
在电脑cmd中输入:
int main()
{
system("shutdown -s -t 60");
printf("请输入我是猪,不然你的电脑会在60s后自动关机\n");
char arr[30] = "我是猪";
char arr2[20] = { 0 };
again:
scanf("%s", arr2);
if (strcmp(arr, arr2) == 0)
{
printf("已取消关机,嘻嘻");
system("shutdown -a");
}
else
{
goto again;
}
}