分支语句和循环语句
C语言是结构化的程序设计语言。所谓的结构化一共抽象成三种结构:1.顺序结构 2.选择结构 3.循环结构
什么是语句
C语言中有一个分号;隔开的就是一条语句。
比如:
printf("hehe\n");
3 + 5;
; //空语句
return 0;
这些都是语句。
分支语句(选择结构)
if语句
语法结构:
注意:C语言表示真和假是通过非0就是真,0就是假从而进行区分的。
总结:
- 单分支:if语句中的表达式如果表达式为非0即为真,运行if语句里的代码;表达式运算的是0就不执行if里面的语句。
- 双分支:if语句中的表达式如果表达式为非0即为真,运行if语句里的代码(语句1),不运行else里的代码(语句2);表达式运算的是0就不执行if里面的语句(语句1),而执行else里的代码(语句2)。
- 多分支:如果if语句中的表达式1和else if语句中表达式2 都不成立,运行else语句 语句3的代码;如果if语句中的表达式1成立运行语句1中的代码,其他分支不执行;如果else if语句中的表达式2成立运行语句2中的代码,其他它分支不执行。
if语句的使用:
1.单分支
#include<stdio.h>
int main()
{
int age = 10;
if (age >= 18)
{
printf("成年\n");
}
return 0;
}
运行结果:
如果上面的age=20
注:if语句后面可以只跟一条语句,也可以不带else
2.双分支
#include<stdio.h>
int main()
{
int age = 10;
if (age >= 18)
{
printf("成年\n");
}
else
{
printf("未成年\n");
}
return 0;
}
if、else语句需要注意以下的使用:
#include<stdio.h>
int main()
{
int age = 23;
if (age >= 18)
{
printf("成年\n");
}
else
printf("未成年\n");
printf("不能谈恋爱\n");
return 0;
}
代码写成这样运行结果:
这样写和下面的代码没有任何区别!
#include<stdio.h>
int main()
{
int age = 23;
if (age >= 18)
{
printf("成年\n");
}
else
printf("未成年\n");
printf("不能谈恋爱\n"); //跟上头的代码没区别!!!
return 0;
}
注意:if 和else 默认只能控制一条语句,要想控制多条语句就要加上 {}
改进:
这里的一对{ }就是一个代码块。
3.多分支
#include<stdio.h>
int main()
{
int age = 20;
if (age < 18)
{
printf("少年\n");
}
else if(18<=age && age<26)
{
printf("青年\n");
}
else if (age >= 26 && age < 40)
{
printf("中年\n");
}
else if (age >= 40 && age < 60)
{
printf("壮年\n");
}
else if (age >= 60 && age <=100)
{
printf("老年\n");
}
else
{
printf("老不死\n");
}
return 0;
}
运行结果:
注意:
1.
2. 多分支语句中else可以写也可以不写,视情况而定
悬空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;
}
运行结果:
解释如下:
总结:
- else跟离它最近的if匹配
- 适当的使用{ }可以使代码的逻辑更加清楚,代码风格很重要
if 书写形式的对比
//代码1
if(condition)
{
return x;
}
return y;
//代码2
if(condition)
{
return x;
}
else
{
return y;
}
//代码3
int num = 1;
if(num == 5)
{
printf("hehe\n");
}
//代码4
int num = 1;
if(5 == num)
{
printf("hehe\n");
}
代码2和代码4更好,逻辑更加清晰,不容易出错。
练习
输出1-100之间的奇数(这里就写出其中一种代码)
#include<stdio.h>
int main()
{
int i = 0;
for (i = 1;i <= 100;i++) //for(int i = 1;i <= 100;i++)这样的写法C++、C99是支持
{
if (i % 2 == 1)
{
printf("%d ", i);
}
}
return 0;
}
switch语句
switch语句也是一种分支语句。常常用于多分支的情况。
switch的语法:
switch(整形表达式)
{
case 整型常量表达式:
语句;
}
注意:case 整型常量表达式:中的整型常量表达式可以是1+2、1、2等等,不能是1.0、n、x 等等 这些变量。此外,字符也可以(字符也属于整型中的一种(字符底层存储用的是字符的ASCII值))
switch语句的使用:
#include<stdio.h>
int main()
{
int day = 0;
scanf("%d", &day);
switch (day)
{
case 1:
printf("星期1\n");
case 2:
printf("星期2\n");
case 3:
printf("星期3\n");
case 4:
printf("星期4\n");
case 5:
printf("星期5\n");
case 6:
printf("星期6\n");
case 7:
printf("星期天\n");
}
return 0;
}
解释:switch表达式的结果是几 ,它就从case后面对应的数进去(入口),执行到switch结束。如果想要实现分支,需要搭配break使用才能实现真正的分支。
在switch语句中的break
上段代码改正如下:
#include<stdio.h>
int main()
{
int day = 0;
scanf("%d", &day);
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;
}
return 0;
}
switch语句中 case是入口;break是出口。这样的搭配才能实现真正的分支
例如:
1-5 工作日
6-7 休息日
#include<stdio.h>
int main()
{
int day = 0;
scanf("%d", &day);
switch (day)
{
case 1:
case 2:
case 3:
case 4:
case 5:
printf("weekday\n");
break;
case 6:
case 7:
printf("weekend\n");
break;
}
return 0;
}
注意:break语句的实际效果是把语句列表划分为不同的部分。break语句不是必须要在case语句中添加的,具体问题具体分析
编程好习惯
在最后一个case语句的后面加上一条break语句。(之所以这么写是可以避免出现在以前的最后一个case语句后面忘记添加break语句)。
default子句
如果switch语句中的表达的值与所有的case标签的值都不匹配时,这时就需要default子句。
例如上段代码如果输入 9的话运行结果什么都没有,因此可以优化为
#include<stdio.h>
int main()
{
int day = 0;
scanf("%d", &day);
switch (day)
{
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;
}
default子句:
1.可以写在任何一个case标签可以出现的位置;
2.switch语句中只能出现一条default子句
3.它可以出现在语句列表的任何位置
编程好习惯
在每个switch语句中都放一条default子句是个好习惯,甚至可以在后边再加一个break。
练习
#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;
}
过程
注:break语句是跳出自身所在的switch语句
循环语句
- while
- for
- do while
while循环
while语法结构:
while(表达式)
循环语句结构
用法: 如果while(表达式)中的表达式成立,运行循环语句结构;否则,不运行。(while跟if语句类似)
例如:打印1-10的数字
#include<stdio.h>
int main()
{
int i = 1;
while (i <= 10)
{
printf("%d ", i);
i++;
}
return 0;
}
while语句中的break和continue
break介绍
上段代码改写为:
总结:break在while循环中的作用其实在循环中只要遇到break,就停止后期的所有的循环,直接终止循环。 所以while中的break是用于永久终止循环的。(在while中break用于永久的终止循环)
continue介绍
continue使用的实例:
此段代码进入死循环了
总结:在while循环中,continue的作用是跳过本次循环 continue后面的代码,直接去判断部分,看是否进行下一次循环。
while循环的应用
代码一
不断地在屏幕上打印字符
#include<stdio.h>
int main()
{
char ch = 0;
//ctrl+z-getchar就读取结束
while ((ch = getchar()) != EOF)
{
putchar(ch); //输出一个字符
}
return 0;
}
补充:
- getchar函数从标准输入获取字符。返回值是该字符的ascll值,否则返回EOF。
- putchar函数在标准输出中输出字符。返回值写入成功返回该字符的ascll值返回,否则返回EOF
EOF - end of file - 文件的结束标志- ctrl+z代表着getchar读到EOF,读取结束
- getchar函数也会读取\n(getchar认为缓冲区中的任何一个字符都是字符包括\n)
代码二
getchar的使用场景
#include<stdio.h>
int main()
{
char password[20] = { 0 };
printf("请输入密码:>");
scanf("%s", password); //password没取地址是因为password本身是个数组,数组的数组名本身就是个地址
printf("请确认密码(Y\N):>");
int ch = getchar();
if (ch == 'Y')
{
printf("确认成功\n");
}
else
{
printf("确认失败\n");
}
return 0;
}
运行结果:
运行结果和优化过程(包含讲解)
最终代码:
#include<stdio.h>
int main()
{
char password[20] = { 0 };
printf("请输入密码:>");
scanf("%s", password);
printf("请确认密码(Y/N):>");
//清理缓冲区去
//getchar();//处理 \n
//处理缓冲区中的多个字符
int tmp = 0;
while ((tmp = getchar()) != '\n')
{
;
}
int ch = getchar();
if (ch == 'Y')
{
printf("确认成功\n");
}
else
{
printf("确认失败\n");
}
return 0;
}
总结:
- getchar、scanf函数都是输入函数,是从缓冲区中拿数据的并不是从键盘上拿数据的
- \n会触发scanf函数从缓冲区中读取内容,scanf函数并不会读取\n或者空格,而getchar函数会读取\n或者空格
- getchar函数读取的是字符返回的是整型是因为getchar读取的是字符而字符本质上是以ASCII值(ASCII值是整型)的形式存储的;getchar函数读取的时候不一定返回的是ASCII值有可能返回EOF(EOF为-1)是整型
代码三
#include<stdio.h>
int main()
{
int ch = 0;
while ((ch = getchar()) != EOF)
{
if (ch < '0' || ch>'9')
{
continue;
}
putchar(ch);
}
return 0;
}
本段代码只打印数字
for循环
语法
for(表达式1,表达式2,表达式3)
循环语句
表达式1为初始化部分,用于初始化循环变量的;表达式2为条件判断部分,用于判断循环什么时侯终止;表达式3为调整部分,用于循环条件的调整。
举个例子:打印1-10
#include<stdio.h>
int main()
{
int i = 0;
for (i = 1;i <= 10;i++)
{
printf("%d ", i);
}
return 0;
}
对比一下for和while循环
总结: 可以发现while循环中依然存在循环的三个必须条件,但是由于风格的问题使得三个部分很可能偏离较远,这样查找修改就不够集中和方便。因此for循环的风格更胜一筹,使用频率较高。
break和continue在for循环中
for循环中的break和continue,它们的意义和while循环中的类似,但是还是有些差异
for循环中的break语句用于终止整个循环的
对比 1
对比 2
注:
- for循环中的continue语句是跳过本次循环continue后面的代码,一旦跳过这段代码之后紧接着就要执行for循环中调整部分中的语句
- while循环中的continue和for循环中的continue是有所区别的:for循环中的continue它跳到调整部分;而while循环中的continue它有可能把调整部分跳过去导致程序死循环
总结:for循环和while循环中的break使用上是一样的;然而在有的时候例如(对比2)continue在for和while循环中的使用,运行结果就不同
for语句的循环控制变量
建议:
1.不可在for循环体内修改循环变量,防止for循环失去控制。
2.建议for语句的循环控制变量的取值采用“前闭后开区间”写法。
一些for循环的变种
变种1.(1)
#include<stdio.h>
int main()
{
//判断部分的省略-判断部分恒为真
for (;;)
{
printf("hehe\n");
}
return 0;
}
注:
- for循环中,初始化部分、判断部分、调整部分都可以省略
- 判断部分的省略,判断部分恒为真,程序可能进入死循环
变种1.(2)
只会打印i=0时的三次hehe。因为内循环中没写初始化默认使用外循环外的j变量,当j=3时外循环无论循环多少次,内循环依旧不满足判断条件不会进入内循环,从而只会打印三次hehe
变种2:
注:for循环中可能存在多个循环变量控制循环
练习
请问循环要循环多少次
#include<stdio.h>
int main()
{
int i = 0;
int k = 0;
for (i = 0, k = 0;k = 0;i++, k++)
k++;
return 0;
}
本段代码循环0次。(这里k=0是赋值不是判断)
过程
do…while()循环
do…whille()语句的语法:
do
循环语句;
while(表达式);
执行流程:无论条件是否成立都先执行一次循环语句;接下来如果条件成立执行循环语句;否则不执行。
例如 :打印数字1-10
执行流程:
do while循环中的break和continue
do while语句的特点:循环至少执行一次,使用的场景有限,所以不是经常使用。
总结:do……while()循环和while循环使用方法类似不过do……while()循环无论判断条件如何都会至少执行一次。这两个循环中break语句和continue语句的使用方法一样
练习
1.计算n的阶乘
#include<stdio.h>
int main()
{
int n = 0;
int i = 0;
int ret = 1;
scanf("%d", &n);
for (i = 1;i <= n;i++)
{
ret *= i;
}
printf("%d", ret);
return 0;
}
2.计算1!+2!+…n!
#include<stdio.h>
int main()
{
int n = 0;
int i = 0;
int ret = 1;
int sum = 0;
scanf("%d", &n);
for (i = 1;i <= n;i++)
{
ret *= i;
sum += ret;
}
printf("%d\n", sum);
return 0;
}
3.在一个有序数组中查找具体的某个数字n(本题用到的是折半查找(二分查找)算法)
思想:
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int k = 7;//要查找的数字
//在arr这个有序数组中查找k=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] < k)
{
left = mid + 1;
}
else if (arr[mid] > k)
{
right = mid - 1;
}
else
{
printf("找到了,下标是:%d\n", mid);
break;
}
}
if (left > right)
{
printf("找不到了\n");
}
return 0;
}
每一次找被查找范围的中间元素的方法,查一次去掉一半数据这种方法——折半查找(二分查找)。时间复杂度为O(logN)
4.编写代码,演示多个字符从两端移动,向中间汇聚。
#include<stdio.h>
#include<string.h>
#include<windows.h>
int main()
{
char arr1[] = "welcome to my family !!!!!!";
char arr2[] = "###########################";
int left = 0;
int right = strlen(arr1) - 1;
while (left <= right)
{
arr2[left] = arr1[left];
arr2[right] = arr1[right];
printf("%s\n", arr2);
Sleep(1000); //休眠一秒钟
system("cls"); //清空屏幕
left++;
right--;
}
printf("%s\n", arr2);
return 0;
}
5.编写代码实现,模拟用户登录情景,并且只能登陆三次
#include<stdio.h>
#include<string.h>
int main()
{
int i = 0;
char password[20] = { 0 };
for (i = 0;i < 3;i++)
{
printf("请输入密码:>");
scanf("%s", password);
if (strcmp(password, "123456") == 0)
{
printf("登陆成功\n");
break;
}
else
printf("密码错误,请重新输入!\n");
}
if (i == 3)
{
printf("三次密码均错误,退出程序\n");
}
return 0;
}
注:
- 两个字符串比较不能使用==去比较,应该使用strcmp函数去比较
- strcmp函数用于比较两个字符串的大小,头文件string.h,返回值如果两个字符串相等返回0;如果第一个字符串大于第二个字符串返回>0;如果第一个字符串小于第二个字符串返回<0;
- 两个字符串比较大小比较的是ASCII值不是字符串长度
- 上述代码中password==“123456”,比较的不是字符串的内容而是两个字符串的首字符的地址
goto语句
使用场景
C语言中提供了可以随意滥用的goto语句和标记跳转的标号。
从理论上goto语句是没有必要的,实践中没有goto语句也可以很容易地写出代码。
但是,某些场合下goto语句还是用得着的,常见的用法就是终止程序在某些深度嵌套的结构的处理过程,例如一次跳出两层或多层循环。
这种情况使用break是达不到目的的。它只能从最内层循环退出到上一层的循环。
goto语句的语法
1
label: statement;
goto label;
2``
goto label;
label: statement;
以上是goto语句的两种基本用法。
例如
#include<stdio.h>
int main()
{
flag:
printf("haha\n");
printf("hehe\n");
goto flag;
return 0;
}
注:goto语句会打乱程序正常执行的流程
实例:一个关机程序
要求:
关机程序
只要运行起来,电脑就在一分钟内关机,如果输入我是猪,就取消关机。
原码
代码一
//关机程序
//只要运行起来,电脑就在一分钟内关机,如果输入我是猪,就取消关机。
//shutdowm -s -t 60 设置在60s之后关机
//shutdown -a 取消关机
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
char input[20] = { 0 };
//关机
//C语言提供了一个函数:system()-执行系统命令的
system("shutdown -s -t 60");
again:
printf("请注意,你的电脑在1分钟内关机,如果输入:我是猪,就取消关机\n");
scanf("%s", input);
if (strcmp(input, "我是猪") == 0)
//两个字符串比较是不能使用==的,应该使用strcmp() string compare
{
system("shutdown -a");
}
else
{
goto again;
}
return 0;
}
这个运行结果就不显示了。
代码二
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
char input[20] = { 0 };
system("shutdown -s -t 60");
while (1)
{
printf("请注意,你的电脑在1分钟内关机,如果输入:我是猪,就取消关机\n");
scanf("%s", input);
if (strcmp(input, "我是猪") == 0)
{
system("shutdown -a");
break;
}
}
return 0;
}
以上两段代码是分别用goto语句和while循环语句写的
goto语句真正合适的使用场景如下:
for(....)
for(....)
{
for(....)
{
if(disaster)
goto error;
}
}
.....
error:
if(disaster)
//处理错误情况
注意:多层嵌套的时候循环用goto语句是比较适合的,除此之外不建议去使用goto语句
例如:
void test()
{
flag:
printf("hehe\n");
}
int main()
{
goto flag;
return 0;
}
注:goto语句只能在一个函数范围内跳转,不能跨函数。
猜数字游戏实现
猜数字游戏实现思路(步骤):
1.自动产生一个1-100之间的随机数
2.猜数字
(1)猜对了,就恭喜你,游戏结束
(2)猜错了,程序会提示你猜大了,还是猜小了,继续猜,直到猜对
3.游戏可以一直玩,除非退出游戏
原码
//猜数字游戏
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
void menu()
{
printf("************************************\n");
printf("********** 1. play **************\n");
printf("********** 0. exit **************\n");
printf("************************************\n");
}
void game()
{
//猜数字游戏的实现
//1.生成随机数
//rand函数返回了一个0-32767之间的数字
//时间-时间戳(时间转换出来的数字)
int ret = rand()%100+1; //%100的余数是0-99,然后+1,范围就是1-100
//2.猜数字
int guess = 0;
while(1)
{
printf("请猜数字:>");
scanf("%d", &guess);
if (guess > ret)
{
printf("猜大了\n");
}
else if (guess < ret)
{
printf("猜小了\n");
}
else
{
printf("恭喜你,猜对了\n");
break;
}
}
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();//打印菜单
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
运行结果:
注:
- 采用do……while()循环是因为可以先玩,玩完之后再选择是否继续游戏
- input变量放到do……while()循环外,这样非常巧妙的作为循环的判断条件又作为switch语句中的整形表达式。当输入0时进入case 0:入口执行当中的语句并且当跳出switch进入循环的判断条件判断出退出循环;当输入1或者其他时进入case 1:或者default:入口执行当中的语句并且当跳出switch进入循环的判断条件判断出条件成立进入循环;如果input变量定义在循环里则做不到循环判断的部分
- rand函数的作用是生成随机数(范围是0-RAND_MAX(32767)),但在使用之前要先调用srand函数为随机生成器注入种子
- time函数会返回一个时间戳,返回的时间戳是调用这个函数的那个时间点和计算机的起始时间之间的时间戳
- srand函数要在rand函数之前调用,但是随机数起点的设置srand函数只需调用一次即可,整个工程中只要把随机值生成起点设置一次就可以了。这样不论如何重新生成随机值都不可能与上次的随机值一样,因为没有重新设置随机值生成起点了(如果srand设置进猜数字游戏函数里,每次玩都会调用会重新设置起点,如果生成两个随机值速度过快会导致这两个随机值是一样的)
- srand函数的使用不需要频繁的设置生成器的起点(它可能是从起点的地方开始生成随机数)
- srand函数如果调用多次的话生成的随机数是不够随机的
- 如果光使用rand函数得到的随机值是固定的;如果前面使用了srand函数但种子是固定的不是变化的那么每次重新启动时得到的随机值的顺序和值都是一样的;如果前面使用了srand函数但种子是变化的不是固定的那么每次重新启动时得到的随机值的顺序和值都是不一样的(srand函数不需要频繁设置)