1.什么是语句?
- 表达式语句
- 函数调用语句
- 控制语句
- 复合语句
- 空语句
本篇主要介绍控制语句
控制语句用于控制的执行流程,以实现程序的各种结构方式,它们由特定的语句定义符组成,C语言有九种控制语句
可以分成以下三类:
- 条件判断语句也叫分支语句:if 语句、switch 语句:
- 循环执行语句:do while语句、while语句、for 语句;
- 转向语句:break 语句、goto 语句、continue 语句、return语句
本编介绍的分支循环语句都属于控制语句
2.分支语句(选择结构)
C语言是结构化的程序设计语言(顺序结构、选择结构、循环结构)
2.1 if语句
语法结构:
//形式1
if(表达式)
语句;
//形式2
if(表达式)
语句1;
else
语句2;
//多分支
if(表达式1)
语句1;
else if(表达式2)
语句2;
else
语句3;
表达式结果为真,执行语句,表达式结果为假,不执行语句
在C语言中,非零表示真,0表示为假
if 和else默认只能控制一条语句,如果想要控制多条语句必须加 { }
#include<stdio.h> int main() { if(表达式) { 语句列表1; } else { 语句列表2; } return 0; }
注意:在C语言中。有两个范围的变量不能像数学中那么写
用&&(两边同时为真,才能执行语句)和 | | (两边只要有一个为真,就可以执行语句)连接
2.1.2 if书写形式对比
悬空else
这里有一个小小的陷阱
注意:else与最近的if语句搭配,不是看和谁对齐
不规范的写代码,不便于阅读和理解,所以必须规范书写习惯,养成好的代码风格
1.变量的命名(有意义,规范)
#include<stdio.h> int main() { char firstname[20]={0};//不便于读取,像下面这样的形式就比较容易读取 char FirstName[20]={0}; char First_name[20]={0}; }
2.空格,空行,换行
int a=10; int a = 10;
第二行代码就比第一行代码看起来更舒服一些
养成好的代码风格, 推荐看《高质量的c-c++编程》
2.2.1 if书写形式的对比
//代码1
if(condition){
return x;
}
return y;
//代码2
if(condition)
{
return x;
}
else
{
return y;
}
上面的这两种书写形式不同,但是它们意思都是相同的,return返回值
但是代码1这种形式不便于理解,会让人误以为返回两个值,所以养成好的代码风格非常重要
//代码3
int num=1;
if(num==5)//这里有时候会误写成num=5没有语法错误,这时这条语句恒为真,编译器不会报错
{
printf("hehe\n");
}
//代码2
int num=1;
if(5==num)//如果这里不小心写成5=num,编译器会报错,因为变量是不能赋值给常量的
{
printf("hehe\n");
}
2.1.3 练习
#include<stdio.h> int main() { int i = 0; scanf("%d",&i); if( i %2 ==1) printf("奇数"); else printf("不是奇数"); return 0; }
1.判断一个数是否为奇数2.输出1-100之间的奇数
#include<stdio.h> int main() { int i=0; for(i=0;i<=100;i++) { if(i%2==1) printf("%d ",i); } return 0; }
2.2 switch 语句
switch语句也是一种分支语句,常常用于对分支的情况
输入1,输出星期一
输入2,输出星期二
输入3,输出星期三
输入4,输出星期四
输入5,输出星期五
输入6,输出星期六
输入7,输出星期日
写成if...else...过于复杂,用switch语句会比较简单一些
switch语句的语法结构
switch(整形表达式)//必须是整型
{
语句项;
}
语句项的形式
case 整型常量表达式;
语句;
#include<stdio.h>
int main()
{
int day=0;
switch(day)
{
case 1:
printf("星期1\n");
break;
case 2:
printf("星期2\n");
break;
case 3:
printf("星期3\n");
break;
case 4:
printf("星期4\n");
break;
case 5:
printf("星期5\n");
break;
case 6:
printf("星期6\n");
break;
case 7:
printf("星期日\n");
break;
}
}
多个case支配同一个语句
1.输入1-5,输出的是"weekday";
2.输入6-7,输出的是“weekend ”
#include<stdio.h>
int main()
{
int n=0;
scanf("%d",&n)
switch(n)
{
case 1:
case 2:
case 3:
case 4:
case 5:
printf("weekday\n");
break;
case 6:
case 7:
printf("weekend\n");
break;
default:
printf("选择错误\n");
break;
}
return 0;
}
每个switch语句中只能出现一条default子句
switch允许嵌套使用
编程好习惯
在每个switch语句总都放一条default子句是个好习惯,甚至可以在后面再加一个break
3.循环语句
- while
- for
- do while
while的语法形式
while(表达式)
循环语句;//表达式为真,执行语句
3.3.1while循环中的break和continue的作用
打印1-10数字
#include<stdio.h> int main() { int i=1; while(i<=10) { printf("%d ",i); i++; } return 0; }
//switch语句中break的作用:终止循环 #include<stdio.h> int main() { int i=1; while(i<=10) { if(5==i) break; printf("%d ",i);//1 2 3 4 i++; } return 0; }
//switch语句中continue的作用:跳过本次循环(也就是说后面的代码不再执行),直接到判断部分,进行下一次循环
#include<stdio.h> int main() { int i=1; while(i<=10) { if(5==i) continue; printf("%d ",i);//1 2 3 4死循环 i++; } return 0; }
#include<stdio.h> int main() { int i=1; while(i<=10) { i++; if(5==i) continue; printf("%d ",i);//2 3 4 6 7 8 9 10 11 //打印结果没有5 } return 0; }
while循环中的
break是用于永久的终止循环
continue跳过本次循环后面的代码,直接去判断部分,进行下一次循环的判断
再看几个代码:
//代码什么意思
//代码1
#include<stdio.h> int main() { int ch=0; while((ch=getchar()!=EOF) putchar(ch); return 0; }
这里的代码设当的修改是可以用来清理缓冲区的
//代码2
getchar的返回类型就是整型,返回读到的字符,当读取错误或者是遇到文件结束,会返回EOF,EOF的值是-1
#include<stdio.h> int main() { char ch='\0'; while((ch=getchar())!=EOF) { } }
再vs2019中ctrl+z三次能终止getchar读取字符,不过这个是vs2019的问题,在别的编译器中,一次ctrl+z就能终止个体getchar读取字符
等你输入密码时,回车一按,没有确认就直接打印出No
这是为什么呢?
scanf、getchar在读取字符时不是直接在键盘上读取字符,scanf、getchar是在输入缓冲区读取字符
第一次读取a,第二次再读取\n
所以当输入密码时,他不会拿走\n,它将你输入的123456拿走放到password,留下的\n放到了ret中,所以还没确认就直接输出No啦,但是我们不希望getchar把\n拿走,我们该怎么办呢?
这时就可以清除\n,但是这种写法是临时的,只是对当前输入的123456\n有用,假设输入
123456 abcdef,这时它也是直接没等确认就直接输出No,scanf这里空格它是不会读取,只读取123456,到getchar时只读取空格,下面ret中getchar读取abcdef,不等确认就直接输出No
此时\n之前的,包括\n都可以被清理掉了,就可以很好的解决问题啦
//代码
这个代码的作用时:只打印数字字符,跳过其他字符
#include<stdio.h>
int main()
{
char ch='\0';
while((ch=getchar())!=EOF)
{
if(ch<'0'||ch>'9')
continue;
putchar(ch);
}
return 0;
}
题外:练习
sizeof(arr)-计算的时数组的总大小,单位是字节
sizeof(arr[ 0 ])-计算的数组元素的大小
strlen计算的时字符数组的大小,并且后面有\0,才能计算大小
double和float的保存精度不同
浮点型数据
在给一个float型数据初始化时要写成0.0f
3.2 for循环
3.2.1 语法
for(表达式1;表达式2;表达式3)
循环语句;
表达式1
表达式1为初始化部分,用于初始化循环变量的
表达式2
表达式2为条件判断部分,用于判断循环时终止
表达式3
表达式3为调整部分,用于循环条件的调整
3.2.2 break和continue在for循环中的作用
for循环中的break用于终止循环,break只能结束它所在的循环,多层嵌套终止循环的需要多个break
continue在for循环中的作用,跳过本次循环到调整部分
3.2.3 for语句的循环控制变量
建议:
1.不可以在for循环体内修改循环变量,防止for循环失去控制
这样只进行一次循环 ,只打印出1
2.建议for语句的循环控制变量 的取值采用“前闭后开区间”写法
int i=0;
//前闭后开的写法
for(i=0;i<10;i++)
{ }
//两边都是闭区间
for(i=0;i<=9;i++)
{}
3.2.4 一些for循环的变种
此时若省略for循环中的初始化部分只能打印10个hehe,因为当i=2是j的值还是上次剩下的值j=10,所以这里只能打印10个hehe与上面的结果有所不同
C99 语法才支持了这种写法
C++支持这种写法
3.2.5 一道笔试题
注意:
这道题有陷阱
判断部分k=0,表达式结果为0,0为假,这个循环不循环
3.3 do...while()循环
3.3.1 do语句的语法
do
循环语句;
while(表达式);
//这个循环至少循环一次
3.3.2 break和continue在do...while循环中的作用
3.
在do...while循环中,break也是用于终止循环
在do...while中continue跳过本次循环(continue后面的代码)到判断部分
3.3.3 do...while语句的特点
循环至少执行一次,使用场景有限,所以不是经常使用
三种循环中最常使用的是for,然后是while,最后是do...while
3.4练习
1.计算n的阶乘
2.
3.在一个有序数组中查找具体的某个数字n(二分查找)
注意二分查找必须是有序的
特点:每次查找都将数据范围减小一半
注意事项:
- 这里的 int mid=(left+right)/2;必须放到循环中,因为每次查找的时候,left和right的二者之一都在改变,所以必须放到while循环中
- break不要忘记加,因为这是在循环中,如果不用break终止循环,这个程序将死循环
那么我们应该怎么写?
把right高于left的部分/2,再加到left上,left和right一样高
这样就不会出现栈溢出的现象
4.编写代码,演示多个字符从两端移动,向中间汇聚
这里是直接出来的,没有那种动态的效果
所以可以每次让它停几秒在打印下一个,这是我们引入Sleep()函数单位是ms,当然使用这个函数需要引头文件#include<windows.h>
这样会有一个动态的效果
这时我们想要在一行就能展示出这种动态的效果,所以每次打印清理掉屏幕,使用库函数system("cls");,使用它也需要引头文件#include<stdlib.h>这个可以清空屏幕,他会将最终的想要展示welcome to bit!!!!也会被清理掉,最后打印一遍welcome to bit!!!!即可
5.编写代码实现,模拟用户登录情景,并且只能登录三次。(只允许输入三次密码,如果密码正确则提示登录成功,如果三次均输入错误,则退出程序)
注意两个字符串比较不能用==来比较,使用库函数strcmp来进行比较,使用库函数需要引头文件#include<string.h>
3.4.1猜数字游戏实现
猜数字游戏的逻辑:
- 电脑生成随机数
- 我们输入数字,看猜大了还是猜小了
每次都需要打印菜单,看是否要玩,所以这个选择玩游戏至少要进行一次,用do...while循环
这里的打印猜数字游戏先暂时代替我们要写的猜数字逻辑,我们每写完一部分程序时都要运行一下,看我们的代码是否有问题,写一部分,测一部分,问题就不会积累在后面
现在我们来实现猜数字游戏的逻辑
1.电脑生成随机数
使用rand函数,生成随机数
生成随机数的范围0~RAND_MAX,转到定义可以看到RAND_MAX的值
但是有没有发现每次想玩的时候,每一把生成的随机数都是一样的
注意仔细看rand函数的使用方法,
使用rand函数之前,用调用srand函数设计随机数的生成器
srand(),()括号内的数字变化时,才生成随机数
是不是懵了!!!
想要生成随机数,必须调用一个随机数才能生成随机数
因为时间时一直变化的,这里我们引入时间戳的概念,时间可以转换为对应的时间戳
C语言中的time函数就能返回一个时间戳,这里time函数的参数是指针类型,我们可以不用,传一个空指针(NULL)即可,但是它返回的是time_t的类型
但是srand函数的返回类型是unsigned int的类型,所以需要强制类型转换
这儿确实生成了随机数,但是有没有发现当你按得太快,它会生成一样的数字
rand函数使用之前确实需要调用srand函数,但不是每次生成随机数时都需要设置一个新的起点,所以我们在整个工程只设置一次即可
有没有发现每次生成随机数得数值都太大了,这样不便于我们猜数字,那咱们让电脑生成1-100的随机数
2.猜数字
整体的一个实现
4.goto语句
C语言中提供了可以随意滥用的goto语句和标记跳转的标号。
从理论上goto语句是没有必要的,实践中没有goto语句也可以很容易的写出代码,但是某些场合下goto语句还是用得着的,最常见的用法就是终止程序在某些深度嵌套的结构的处理过程
例如:一次跳出两层或多层循环
多层循环这种情况使用break是达不到目的的。他只能从最内层循环退出到上一层的循环
这里一直不停的打印hehe和haha
goto语句会打乱原本的程序的执行流程,跳来跳去的,可能会让程序出现问题
所以尽可能少使用goto语句,goto函数不能跳函数,只能在一个函数内部跳转
goto语言真正适合的场景如下:
for(...)
for(...)
{
for(...)
{
if(disaster)
goto error;
}
}
...
error:
if(disaster)
//处理错误情况
下面是使用goto语句的一个例子,然后使用循环的实现方式替换goto语句
一个关机程序
shutdown -s 设置关机
-t 设计倒计时关机时间
shutdown -a 停止关机
int 、struct、continue都是关键字,但是像#define 、#include等不是关键字,而是预处理指令,基本上以#开头的都是预处理指令
在switch语句中continue不死switch的关键字,continue是用于循环中,switch不是循环语句