c语言是结构化的程序设计语言。
在实际生活中一般就有三种情况顺序、选择、循环,C语言是为了解决生活中的问题,当然在其设计时支持顺序结构、循环结构和选择结构。在本篇文章中主要是对这三种结构在C语言中的具体使用来进行说明。
目录
3.3.3 do while循环中的break和continue
在c语言中使用一些语句来表达一些结构;
分支语句(选择结构):
- if
- switch
循环语句(循环结构):
- while
- for
- do while
一、什么是语句?
c语言可以将语句分为以下5类:
- 表达式语句 例如2+3;
- 函数调用语句 例如Add(a,b);
- 控制语句
- 复合语句
- 空语句 仅有一个;,一个;就代表一个空语句。
在下文我们主要介绍的是控制语句。
控制语句用于控制程序的执行流程,以实现程序的各种结构方式(c语言支持三种结构:顺序结构、选择结构、循环结构),他们由特定的语句定义符组成,c语言有9种控制语句。
可以分为以下3类:
- 条件判断语句也叫分支语句:if语句、switch语句;
- 循环执行语句:do while语句、while语句、for语句;
- 转向语句:break语句、goto语句、continue语句、return语句。
二、分支语句(选择语句)
2.1 if语句
那么if语句的语法是什么呢?
语法结构:
if(表达式) //表达式的结果为真,语句执行 在c语言中0表示假,非0为真
语句; //表达式为假,语句不执行
if语句有单分支、双分支、以及多分支类型:
具体举例为:
//单分支
#include <stdio.h>
int main()
{
int age = 0;
scanf("%d", &age);
if (age < 18)
{
printf("未成年\n");
}
return 0;
}
//双分支
#include <stdio.h>
int main()
{
int age = 0;
scanf("%d", &age);
if (age < 18) //如果age<18,则打印未成年
{
printf("未成年\n");
}
else //否则,打印成年
{
printf("成年\n");
}
return 0;
}
//多分支
#include <stdio.h>
int main()
{
int age = 0;
scanf("%d", &age);
if (age < 18) //如果age<18,则打印未成年
{
printf("未成年\n");
}
else if(age >= 18 && age < 30) //,age >= 18 && age < 30,打印青年
{
printf("青年\n");
}
else if (age >= 30 && age < 50)
{
printf("中年\n");
}
else if (age >= 50 && age < 80)
{
printf("老年\n");
}
else
{
printf("老寿星\n");
}
return 0;
}
在这里我们需要注意的是:在if或者else后面都是默认只能控制一条语句的,若其后要执行多条语句,就应该加{}(即代码块,一对{}就是一个代码块)。
#include <stdio.h>
int main()
{
if (表达式)
{
语句列表1; //多条语句
}
else (表达式)
{
语句列表2; //多条语句
}
return 0;
}
2.1.1悬空else(if与else的匹配问题)
else的匹配:else是和他最近的if匹配的。
例如我们写一个代码
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
if (a == 1)
if (b == 2)
printf("hehe\n");
else
printf("haha\n");
return 0;
}
或许许多初学者会认为运行结果是haha但实际是不输出。认为输出haha的同学会把第一个if与else匹配,这样是不对的,if与else语句的配对遵守的是就近匹配原则。
解析:
在上述代码中a=0,此时不满足a==1就不会进入if语句中,那么在if语句中嵌套的if与else语句也不会执行。
在这里我们想是否可以通过一些{}使代码的逻辑更加清楚呢,增加代码的可读性,也就是说形成好的代码风格。
上面的代码使用好的代码风格改进之后就变成了,可读性增强
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
if (a == 1)
{
if (b == 2)
{
printf("hehe\n");
}
else
{
printf("haha\n");
}
}
return 0;
}
2.1.2if书写形式的对比
1.
//代码一
if (condition) {
return x;
}
return y;
//此代码含义为如果if后面的条件满足返回x否则返回y
//注意return只要返回了,后面的return就无法执行了
//代码二
if (condition)
{
return x;
}
else
{
return y;
}
代码一与代码二运行结果一致,且算法思想一致但是对比之下我们发现代码2的可读性高,且代码风格好。
2.
//代码三
int num = 1;
if (num == 5)
{
printf("hehe\n");
}
代码三是一个很简单的代码,但是如果我们粗心将表达式(if(表达式))中num==5误写成了num=5时,代码不会报错,但是会导致代码运行结果与预期结果不符。如果在一个大的工程中将num==5写为了num=5,编译器不会报错,我们也无法及时知道代码的出错点,会降低时间效率。
那么我们怎么尽可能避免这个问题呢?
于是就有了代码四:
//代码四
int num = 1;
if (5 == num)
{
printf("hehe\n")
}
在代码四中表达式中的常量放在了左边,变量放在了右边。此时如果我们不小心少写了一个=,写成if(5 = num)就会直接报错,编译无法通过,更容易发现错误,减少bug。
在上述四个代码中,我们发现好的代码风格会减少代码的出错率,上述中代码二与代码四都是好的代码风格,通过总结可以归结为以下两点:
- 通过适量的代码块({})来提升可读性
- 在表达式中常量放在左边,变量放在右边,例如5==num
2.1.3练习题
- 判断一个数是否为奇数
- 输出1-100之间的奇数
题1:
#include <stdio.h>
int main()
{
int a = 0;
scanf("%d", &a);
if (a % 2 != 0)
{
printf("是奇数\n");
}
return 0;
}
题2:
#include <stdio.h>
int main()
{
int a = 0;
for (a = 1; a <= 100; a++)
{
if (a % 2 != 0)
{
printf("%d ", a);
}
}
return 0;
}
2.2 switch语句
switch也是一种分支语句,常用于多分支的情况之下。
比如:
输入1,输出星期一输入2,输出星期二
输入3,输出星期三
输入4,输出星期四
输入5,输出星期五
输入6,输出星期六
输入7,输出星期日
这种情况下使用if......else的形式会太复杂,那么我们就有了switch这种分支语句。
switch的语法结构:
switch(整型表达式)
{
语句项;
}
在这里语句项是什么呢?
//是一些case语句
//如下:
case 整型常量表达式:
语句;
2.2.1在switch语句中的break
我们来看一个代码想要实现
输入1,输出星期一
输入2,输出星期二
输入3,输出星期三
输入4,输出星期四
输入5,输出星期五
输入6,输出星期六
输入7,输出星期日
在这里我们发现结果与我们想要的不符,为什么呢?
因为在case语句中我们未加break导致case语句一直向下进行.
在switch语句中,我们无法直接实现分支,搭配break使用才能实现真正的分支。
即case表达式决定入口,break决定出口,跳出switch。
那这个代码应该怎么写?
#include <stdio.h>
int main()
{
int a = 0;
scanf("%d", &a);
switch (a)
{
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后必须有break?
答案是不是,case后面有无break是根据具体逻辑来看.
如果要求写一个代码满足以下条件:
- 输入1-5,输出的是"weekday".
- 输入6-7,输出的是"weekend"
#include <stdio.h>
int main()
{
int a = 0;
scanf("%d", &a);
switch (a)
{
case 1:
case 2:
case 3:
case 4:
case 5:
printf("weekday\n");
break;
case 6:
case 7:
printf("weekend\n");
break;
}
return 0;
}
在此例中就不需要每个case后面都有break.
break语句的实际效果时把语句列表划分为不同的分支部分.
2.2.2default子句
如果表达的值与所有的case标签的值都不匹配怎么办?
其实也没什么,结构就是所有的语句都被跳过而已.
但是如果你不想忽略不匹配所有标签的表达式的值该怎么办?
这时就有了default子句.
当switch表达式的值并不匹配所有case标签的时候,这个default子句后面的语句就会执行,所以每一个switch语句中只能出现一条default子句.
注意,default可以出现在语句列表的任何位置,而且语句流会像执行一个case标签一样执行default子句.
2.2.3练习
下面代码的执行结果是什么:
#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;
}
运行结果是什么呢,接下来我们分析一下:
三、循环语句
-
while
-
for
-
do while
3.1while循环
当同一件事情需要做许多次时,在c语言中怎么办呢?
c语言中给我们引入了:while语句,可以实现循环。
//while循环语法结构
while(表达式) 表达式为真,执行语句,执行后再返回while判断真假
循环语句;
例如我们实现:
在屏幕上打印1-10的数字。
#include<stdio.h>
int main()
{
int i = 1;
while (i <= 10)
{
printf("%d ", i);
i = i + 1;
}
return 0;
}
注意while循环是默认控制一条语句,若要控制多条语句则需要使用代码块({});
3.1.1while语句中的break和continue
在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;
}
此时运行结果为
我们发现5及5以后的数字没有被打印。
由此我们可以知道break执行的时候终止了循环,让循环不再继续。
总结:
break在while循环中的作用:
- 在循环中只要遇到break,就停止后期的所有循环,直接终止循环
- while循环中的break适用于永久停止循环的
在while循环中的continue有什么作用呢?
我们也使用代码来解释:
1.
#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然后进行死循环。
为什么会这样呢?那是因为continue在循环中是跳过本次循环continue后面的代码,直接去判断部分判断。
2.
#include<stdio.h>
int main()
{
int i = 0;
while (i < 10)
{
i = i + 1;
if (i == 5)
continue;
printf("%d ", i);
}
return 0;
}
总结:
continue在while循环中的作用是:
- continue是用于终止本次循环的,也就是说本次循环中continue后面的代码不会再执行直接跳到while语句的判断部分,进行下一次循环的入口判断。
3.1.3scanf即getchar读取相关问题
我们再来看几个代码:
//代码一
#include<stdio.h>
int main()
{
int ch = 0;
while ((ch = getchar()) != EOF)
putchar(ch);
return 0;
}
接下来我们来解析一下这个代码:
在这里我们从键盘输入一个字符,再通过putchar函数打印出来。
在这里我们发现从键盘输入字符的时候,当输入字符时会按下回车键,在程序运行结果中回车键也被打印出来了。这就说明getchar可以接收到回车键,putchar可以将回车键打印出来,那么他究竟有什么实际用途呢,下面我们通过一写代码来表示。
我们想要有一个代码实现以下功能:
- 用户从键盘输入密码
- 让用户确认密码
在接下来的代码中我们将会一步步改进直到达到想要的结果;
第一版:
#include<stdio.h>
int main()
{
char password[20] = { 0 };
printf("请输入密码:\n");
scanf("%s", password);
printf("请确认密码(Y/N):\n");
int ch = getchar();
if (ch == 'Y')
{
printf("确认成功\n");
}
else
{
printf("确认失败\n");
}
return 0;
}
运行结果为:
在这里我们预想结果是输入密码然后输入Y或者N最终打印确认失败或者确认成功,但是在此处运行时我们发现在输入密码之后直接就确认失败了,这是为什么呢?
我们此处插入scanf以及getchar从键盘读取字符的相关知识:
scanf工作原理:scanf要读数据,他首先去输入缓冲区看是否有数据,如果有数据就读取,如果没有数据,就等待键盘输入。
getchar工作原理与scanf相同,都是先从缓冲区看,如果有数据就读取,如果没有数据,就等待键盘输入。
具体我们可以使用图解:
注意的是:scanf从缓冲区读取时不会读取回车,回车仅仅是为了数据那能够放入缓冲区。
在上述代码中,scanf先去输入缓冲区看是否有数据发现了有,就读取了hgsd即回车前面的字符,然后程序向下运行,运行到 int ch = getchar();语句时,getchar函数先去输入缓冲区看是否有数据,此时在输入缓冲区中有没有被scanf读取的回车键即‘'\n',这时getchar就读取了回车键,然后进入if else语句中,发现回车即\n!=Y这时就会打印确认失败。
那么我们怎么改进这一问题呢?
我们想scanf函数读取后,输入缓冲区里面留下了‘\n’所以getchar直接将其读取了,那么我们在int ch = getchar();语句之前可以再写一个getchar函数将缓冲区里面的回车读取了在之后的gechar函数读取时发现输入缓冲区里面没有数据,就会等待键盘输入,这样,问题就解决了,那么我们来看一下代码吧!
#include<stdio.h>
int main()
{
char password[20] = { 0 };
printf("请输入密码:\n");
scanf("%s", password);
getchar();
printf("请确认密码(Y/N):\n");
int ch = getchar();
if (ch == 'Y')
{
printf("确认成功\n");
}
else
{
printf("确认失败\n");
}
return 0;
}
此时的运行结果就对了!
刚刚我们说scanf不会读取使数据放入缓冲区的'\n',那么scanf在输入缓冲区读取时还有什么需要注意的点吗,当然是有的,scanf读取字符串时遇到空格时便不再读取,空格也不会被读取。
我们用代码来说明:
int main()
{
char password[20] = { 0 };
printf("请输入密码:\n");
scanf("%s", password);
getchar();
printf("请确认密码(Y/N):\n");
int ch = getchar();
if (ch == 'Y')
{
printf("确认成功\n");
}
else
{
printf("确认失败\n");
}
return 0;
}
当我们输入密码中有空格时,我们发现和第一次情况相同,在输入密码后直接输出了确认错误,此时有的同学可能会疑惑我们不是已经在 int ch = getchar();语句之前再写了一个getchar函数了吗为什么还会出错,要注意getchar函数只能读取一个字符,而在这里scanf未读取空格即之后的字符,字符数量多,当然会出错。
那么我们又需要对代码进行改进:
我们想到可以使用循环。
#include<stdio.h>
int main()
{
char password[20] = { 0 };
printf("请输入密码:\n");
scanf("%s", password);
//清理
int tmp = 0;
while ((tmp = getchar()) != '\n')
{
; //读到'\n'且把'\n'读走停下来
}
printf("请确认密码(Y/N):\n");
int ch = getchar();
if (ch == 'Y')
{
printf("确认成功\n");
}
else
{
printf("确认失败\n");
}
return 0;
}
这时改进完成。
3.2 for循环
3.2.1 for循环的语法
for(表达式1; 表达式2; 表达式3)
循环语句;
其中
表达式1:
表达式1是初始化部分,用于初始化循环变量的。
表达式2:
表达式2是条件判断部分,用于判断循环是否终止。
表达式3:
表达式3是调整部分,用于循环条件的调整。
我们来举个例子以便大家更好地理解for循环
使用for循环在屏幕打印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;
}
在此处我们来比较一下while循环与for循环:
#include <stdio.h>
int main()
{
int i = 0;
//实现相同的功能,使用while
i = 1; //初始化部分
while (i <= 10)
{
printf("haha\n");
i = i + 1; //调整部分
}
//实现相同的功能,使用for
//for (i = 1/*初始化*/; i <= 10/*判断部分*/; i++/*调整部分*/)
for (i = 1; i <= 10; i++)
{
printf("haha\n");
}
return 0;
}
我们可以发现在while循环中依然存在循环的三个必然条件,但是由于风格的问题使得三个部分很可能偏离较远,这样查找修改就不够集中和方便。
3.2.2 break和continue在for循环中
在for循环中break和continue的作用与在while循环中大致相同,但也有小部分不同。
我们使用代码来说明:
#include<stdio.h>
int main()
{
int i = 0;
for (i = 1; i <= 10; i++)
{
if (i == 5)
break;
printf("%d ", i);
}
return 0;
}
此时break含义任然是跳出整个循环。
#include<stdio.h>
int main()
{
int i = 0;
for (i = 1; i <= 10; i++)
{
if (i == 5)
continue;
printf("%d ", i);
}
return 0;
}
此时continue的作用是跳过for循环{}中continue后面的代码,来到调整部分。这里与while循环中continue不同,while循环中continue是来到判断部分。
3.2.3 一些for循环的变种
代码一、
#include <stdio.h>
int main()
{
for (; ; )
{
printf("haha\n");
}
return 0;
}
在上述代码中会一直死循环的一直输出haha
为什么呢?
//for循环中的初始化部分,判断部分、调整部分都是可以省略的,但是不建议初学者省略,容易导致问题。注意省略for循环中的判断部分就意味着判断恒为真。
代码二、
#include <stdio.h>
int main()
{
int i = 0;
int j = 0;
for (i = 0; i < 10; i++)
{
for (j = 0; j < 10; j++)
{
printf("haha\n");
}
}
return 0;
}
//代码运行结果是打印100次haha
如果上述代码中省略初始化部分,会打印多少次haha呢?
#include <stdio.h>
int main()
{
int i = 0;
int j = 0;
for ( ; i < 10; i++)
{
for ( ; j < 10; j++)
{
printf("haha\n");
}
}
return 0;
}
为什么会这样呢?
当我们省略了初始化部分for循环中嵌套的for循环执行完后j=10,不会初始化,此时外层的for循环再次i++后无法再次进入for循环中嵌套的循环。
代码三、
代码三说明可以使用多于1个变量来控制循环。
#include <stdio.h>
int main()
{
int x, y;
for (x = 0, y = 0; x < 2 && y < 5; ++x, y++)
{
printf("hehe\n");
}
return 0;
}
3.2.4 一道小题
//请问循环要循环多少次?
#include <stdio.h>
int main()
{
int i = 0;
int k = 0;
for (i = 0, k = 0; k = 0; i++, k++)
k++;
return 0;
}
其实在上题中循环一次都不执行,在for循环的判断部分是一个赋值语句,赋值语句的结果就是所赋的值,即上述判断部分为0,0表示假,循环一次都不执行。
3.3 do......while()循环
3.3.1do......while 循环的语法
do
循环语句;
while(表达式);
3.3.2 特点
循环至少会执行一次。
注意do后面需要执行多条语句时,需要加一个代码块({})。
#include <stdio.h>
int main()
{
int i = 1;
do
{
printf("d ", i);
} while (i <= 10);
return 0;
}
3.3.3 do while循环中的break和continue
在do while循环中break与continue的作用与while循环中相似
我们通过代码来表现:
1.
#include <stdio.h>
int main()
{
int i = 1;
do
{
if (5 == i)
break; //永久的终止循环
printf("%d ", i);
i++;
} while (i <= 10);
return 0;
}
2.
#include <stdio.h>
int main()
{
int i = 1;
do
{
if (5 == i)
continue; //跳过本次循环之后的代码去判断部分
printf("%d ", i);
i++;
} while (i <= 10);
return 0;
}
//运行结果是1 2 3 4 然后死循环
3.
#include <stdio.h>
int main()
{
int i = 0;
do
{
i++;
if (5 == i)
continue; //跳过本次循环之后的代码去判断部分
printf("%d ", i);
} while (i < 10);
return 0;
}
四、goto语句
c语言中提供了可以随意滥用的goto语句和标记跳转的符号。
goto语句最常见的用法是终止程序在某些深度嵌套的结构的处理过程。
goto语句真正适合的场景
for(...)
for(...)
{
for(...)
{
if(disaster)goto error;
}}
...
error:
if(disaster)
//处理错误情况
一个关机程序
在写程序时我们先简单介绍一下:
//shutdown -s -t 60 表示的是60s之后关机
//其中shutdown -s是电脑中的关机命令 后面加-t是设置关机时间
//shutdown -a表示的是取消关机
使用goto语句写:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char input[10] = { 0 };
system("shutdown -s -t 60"); //system函数用于执行系统命令的库函数
again:
printf("电脑将在1分钟内关机,如果输入:我是猪,就取消关机!\n请输入:");
scanf("%s", input);
if (0 == strcmp(input, "我是猪"))
{
system("shutdown -a");
}
else
{
goto again;
}
return 0;
}
使用while循环写:
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
int main()
{
char input[10] = { 0 };
system("shutdown -s -t 60"); //system函数用于执行系统命令的库函数
while (1)
{
printf("电脑将在1分钟内关机,如果输入:我是猪,就取消关机!\n请输入:");
scanf("%s", input);
if (0 == strcmp(input, "我是猪"))
{
system("shutdown -a");
break;
}
}
return 0;
}