当前位置: 首页 > news >正文

分支和循环语句

C语言是结构化的程序设计语言

C语言有3种结构:顺序结构,选择结构,循环结构

今天给大家介绍分支和循环语句,也就是选择和循环结构

什么是语句?

C语言中由一个分号 ; 隔开的就是一条语句。 比如:

分支语句(选择结构)

如果你好好学习,校招时拿一个好offer,走上人生巅峰。
如果你不学习,毕业等于失业,回家卖红薯。
这就是选择。

if 语句

if语句的语法结构(如果表达式的结果为真,则语句执行)
语法结构:
if(表达式)语句;if(表达式)语句1;
else语句2;//多分支    
if(表达式1)语句1;
else if(表达式2)语句2;
else语句3;

eg1:

eg2:单分支

eg3:多分支

在C语言中如何表示真假?

0表示假,非0表示真。

代码块

如果条件成立,要执行多条语句,怎应该使用代码块?

这里一个{ }就是一个代码块

悬空else

现在有这样的一段代码,问他的执行结果是什么?

实际情况他是不执行的

可能有些人认为这段代码执行打印haha,认为if 和 else是相互匹配的,但是实际上这个else 压根就不是和 if(a==1)匹配的,这个else是和if(b==2)匹配的。所以这段代码的结果是啥都不打印。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");
}

这几个代码中,代码1和代码2表达的意思都是相同的,代码3和代码4表达的意思是相同的。但是相对来说代码2和代码4更好,因为代码2表达的更加清晰。代码4可以规避将==写成=(赋值)的错误

switch语句 

switch语句也是一种分支语句。 常常用于多分支的情况。

比如:

输入1,输出星期一

输入2,输出星期二

输入3,输出星期三

输入4,输出星期四

输入5,输出星期五

输入6,输出星期六

输入7,输出星期七

那我没写成 if...else if ...else if 的形式太复杂,那我们就得有不一样的语法形式。 这就是 switch 语句。

switch(整型表达式)
{语句项;
}//是一些case语句:
//如下:
case 整形常量表达式:语句;

这里注意的点就是switch()这给括号里面是整形表达式,case 旁边是整型常量表达式

switch语句中的 break

switch语句中,我们没法直接实现分支,搭配break使用才能实现真正的分支。

eg: 这段代码中我们发现如果输入2就会执行全部的case,不是我们所期望的,输入2执行case2,这个时候break就派上用场了,break的作用就是执行case后,直接跳出switch语句

改进

eg2:  如果我们将day改成float类型是会直接报错的,他必须是整形

eg3: 我们将case 旁的写成变量是不行的,它必须是整型常量表达式

有时候我们的需求变了:
        1. 输入1-5输出的是“weekday”;
        2. 输入6-7输出“weekend”
所以我们的代码就应该这样实现了

break语句的实际效果是把语句列表划分为不同的部分。

编程好习惯

在最后一个 case 语句的后面加上一条 break语句。 之所以这么写是可以避免出现在以前的最后一个 case 语句后面忘了添加 break语句

default子句

如果表达的值与所有的case标签的值都不匹配怎么办?

        其实也没什么,结构就是所有的语句都被跳过而已。 程序并不会终止,也不会报错,因为这种情况在C中并不认为适合错误。

但是,如果你并不想忽略不匹配所有标签的表达式的值时该怎么办呢?

        你可以在语句列表中增加一条default子句,把下面的标签 default: 写在任何一个case标签可以出现的位置。 当 switch表达式的值并不匹配所有case标签的值时,这个default子句后面的语句就会执行。

        所以,每个switch语句中只能出现一条default子句。 但是它可以出现在语句列表的任何位置,而且语句流会像贯穿一个case标签一样贯穿default子句

在每个 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){//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

比如你开上学后就开始买彩票,如果中了500万循环直接终止,迎娶白富美。如果没有中的话,就老实学习,知道中了500万或者成为大牛才终止循序 

while循环

我们已经掌握了,if语句:
if(条件)语句;

当条件满足的情况下,if语句后的语句执行,否则不执行。但是这个语句只会执行一次。

        但是我们发现生活中很多的实际的例子是:同一件事情我们需要完成很多次。 那我们怎么做呢? C语言中给我们引入了:while语句,可以实现循环。
//while 语法结构
while(表达式)循环语句;

例如最简单的一个while循环,无线循环执行打印hello world操作

比如我们实现:在屏幕上打印1-10的数字

while语句中的breakcontinue

break介绍

这里代码输出的结果是什么?
1 2 3 4
1 2 3 4 5
1 2 3 4 5 6 7 8 9 10
1 2 3 4 6 7 8 9 10

答案:

我们看到

breakwhile循环中的作用:
其实在循环中只要遇到break,就停止后期的所有的循环,直接终止循环。 所以:while中的 break是用于永久终止循环的
continue介绍
这里代码输出的结果是什么?
1 2 3 4
1 2 3 4 5
1 2 3 4 5 6 7 8 9 10
1 2 3 4 6 7 8 9 10
答案:
我们可以看到当前代码,在输出到4的时候卡住了。这个是因为continue的作用是跳出当次循环,上述代码在i变成5以后,遇到continue,跳过此次循环,所以i不进行+1操作,然后进行判断i<=10,此刻i还是5,遇到continue继续跳过此次循环,然后重复执行该操作,所以最终输出的结果就是1,2,3,4然后循环循环...
总结: continue在while循环中的作用就是:
continue是用于终止本次循环的,也就是本次循环中continue后边的代码不会再执行,而是直接
跳转到while语句的判断部分。进行下一次循环的入口判断.
这个代码的输出结果呢?
下面看这样的一段代码
putchar 与getchar简介

他俩的作用就是一个从键盘读取一个字符,一个将字符输出到屏幕上,相当于scanf与printf
EOF的值就是-1,windows下的EOF就是crtl+z+enter。getchar返回值是int类型就是因为字符的本质也是ASCII码值,本质还是整数。
所以这段代码的意思就是你输入字符,然后我给你打印字符。
另外就是可能有些人有一个疑问,eof的值不是-1吗,为啥当我运行程序的时候,我输入-1,仍然显示出来了,而不是将程序进行终止了?

问题的核心在于理解 getchar() 函数如何工作:

  1. getchar() 读取的是字符,不是数字

    • 当你在键盘上输入 -1 时,实际上输入的是两个字符:- 和 1
    • 字符 - 的 ASCII 值是 45
    • 字符 1 的 ASCII 值是 49
    • 这些都不等于 EOF(通常是 -1)

那这段代码的应用场景呢?

有这样的一段代码,但是当我们输入完密码,按下回车键以后,发现还没有进行确认密码,直接就给我们输出密码错误。这是怎么回事呢?

这是因为scanf与getchar读取数据的时候是从缓冲区里面进行去取的,虽然直观上来看是从键盘上进行读取的,但是实际上中间还存在一个输入缓冲区。

所以,这就会造成即使我们输入正确的密码,也直接会显示密码错误。该如何解决呢?

造成这种问题的原因,就是因为getchar在执行之前,缓冲区里面还存在数据,所以我们需要将缓冲区里面的数据进行清理干净。

有人说,直接在加一个getchar,这样不就行了吗,这种方法只能看似可以,实际是有局限性的

正确解决方法:同时这也是刚刚提到的那段代码的应用场景

ps:如果我们想要我们的密码包含空格,我们就需要使用gets函数

这段代码的意思是什么呢?

我们现在就很容易知道,这段代码的意思就是只输出0-9之间的字符,别的字符不输出

小问题 

在执行两个代码的时候, 比如在执行代码1 ,我在第一行输入1以后,他就在第二行给我显示1,但是我还能继续在第一行输入,不会自动进行换行。 在执行代码2的时候,我在第一行输入1以后,他就在第二行给我显示1,并且自动换行,让我下次再第三行输入,这是为啥呢?

这是一个很好的观察!这种现象的原因与输入缓冲机制字符处理方式有关:

代码1的行为分析

代码1中有这段逻辑:

if (ch < '0' || ch > '9')
{continue;
}
putchar(ch);

当输入字符时:

  1. 输入 1 → getchar() 读取到 '1',满足数字条件,putchar('1') 输出 1
  2. 按回车键 → getchar() 读取到 '\n'(换行符),但因为不是数字,执行 continue,跳过了 putchar()
  3. 由于换行符没有被输出,光标仍在同一行,可以继续输入

代码2的行为分析

代码2没有过滤逻辑:

while ((ch = getchar()) != EOF)
{putchar(ch);
}

当输入字符时:

  1. 输入 1 → getchar() 读取到 '1'putchar('1') 输出 1
  2. 按回车键 → getchar() 读取到 '\n'putchar('\n') 输出换行符
  3. 换行符被输出,所以光标移到下一行

关键区别:代码1过滤掉了换行符('\n'),所以不会自动换行;代码2会输出所有字符包括换行符,所以会自动换行到下一行。

这就是为什么代码1感觉"还能继续在第一行输入",而代码2会"自动换行在第三行输入"的原因。

 for循环

我们已经知道了while循环,但是我们为什么还要一个for循环呢? 首先来看看for循环的语法:
for(表达式1;表达式2;表达式3)循环语句;
表达式1 表达式1初始化部分,用于初始化循环变量的。 表达式2 表达式2条件判断部分,用于判断循环时候终止。 表达式3 表达式3调整部分,用于循环条件的调整。
eg:打印1-10
for循环的执行流程图:
现在我们对比一下for循环和while循环:

可以发现在while循环中依然存在循环的三个必须条件,但是由于风格的问题使得三个部分很可能偏离较远,这样查找修改就不够集中和方便。所以,for循环的风格更胜一筹。 for循环使用的频率也最高。
breakcontinuefor循环中

我们发现在for循环中也可以出现breakcontinue,他们的意义和在while循环中是一样的。 但是还是有些差异:

对于break来讲,与while循环中的break没有什么大的区别

对于continue来讲,它俩就存在细微差别

我们发现基本上是一致的代码,for循环就不会造成死循环,while就会造成死循环

这是因为for循环中的continue跳过了后面的代码,直接去了调整部分(也就是i++),调整循环变量,不容易造成死循环。

在while循环中,continue跳过continue后面的代码,直接去了判断部分,所以就造成了死循环

for语句的循环控制变量

一些建议:
1. 不可在for 循环体内修改循环变量,防止 for 循环失去控制。
2. 建议for语句的循环控制变量的取值采用“前闭后开区间”写法。

eg1:在for循环体内修改了循环变量

eg2:

一些for循环的变种 

1.初始化,判断,调整3个部分都省略了

2.判断部分省略,意味条件恒为真

虽然他们可以省略,但尽量还是不要省略。

eg: 这个代码就把初始化部分给省略了,代码本来的意思是想打印100个hehe,但是因为省略了初始化部分,当i=2的时候,j变成了10(并没有重置),终止打印hehe的操作,所以最终内部的for只循环了10次

3.这些初始化,判断,调整的条件可以有多个

一道笔试题

 答案是0次,因为判断条件恒为假,所以压根就不会进行循环

do...while()循环

do语句的语法
do循环语句;
while(表达式);

 执行流程

do语句的特点
循环至少执行一次,使用的场景有限,所以不是经常使用
do while循环中的breakcontinue

do while和while 中的break与continue一致

break直接跳出循环

 continue后一致无限循环

练习 

1. 计算 n的阶乘。
但是因为int是有范围的,严格意义上的n的阶乘是不能这么来写的,下面这段代码是有局限性的
2. 计算 1!+2!+3!+……+10!
简化版代码
3. 在一个有序数组中查找具体的某个数字n。 编写int binsearch(int x, int v[], int n); 功能:在v[0]
<=v[1]<=v[2]<= ….<=v[n-1]的数组中查找x.
最为简单的遍历方法
二分查找
4. 编写代码,演示多个字符从两端移动,向中间汇聚。
5. 编写代码实现,模拟用户登录情景,并且只能登录三次。(只允许输入三次密码,如果密码正确则提示登录成,如果三次均输入错误,则退出程序。

strcmp是比较字符串是否相等的一个函数,相同返回0.

goto语句 

C语言中提供了可以随意滥用的 goto语句和标记跳转的标号。
从理论上 goto语句是没有必要的,实践中没有goto语句也可以很容易的写出代码。
一个简单的goto程序,打印一个hehe,goto在跳转到again,然后在打印hehe,最终程序进入了死循环。 而且这个代码我们完全可以用for来进行代替,所以goto是可有可无的。
但是某些场合下goto语句还是用得着的,最常见的用法就是终止程序在某些深度嵌套的结构的处理过程,例如一次跳出两层或多层循环。
这种情况使用break是达不到目的的。它只能从最内层循环退出到上一层的循环。

比如说下面这种情况,我有三层循环,我想直接从最内层的循环中跳出去,就可以使用goto语句,break只能跳出当前循环。 

for(...)for(...){for(...){if(disaster)goto error;}}…
error:if(disaster)// 处理错误情况

一个关机程序:如果想整蛊你的朋友,请把debug改成release,然后把在release下编译好的程序发给你的朋友。

使用goto版,system就是系统调用,在Windows终端输入的指令 

不使用goto版本,其实就是用循环代替goto

猜数字游戏 

rand,srand,time函数的使用

rand函数是一个随机数生产的函数,但是我们直接使用,就会发现它每次生产的数字都是一样的

这时候就需要对rand进行一个初始化,需要用到srand函数

srand的作用就是设置rand的起点

我们可以看到随着srand的参数不同,生成的随机数确实发生了变化

因此要想让ret不断的变化,就必须保证srand里面也是一个随机数,这貌似与我们正在做的形成了一个悖论,我们想要的就是一个随机数,而srand的参数也需要一个随机数。

这个时候就需要用到时间戳,时间戳(Timestamp)是指从格林威治时间1970年1月1日00时00分00秒起至现在的总秒数。这个概念在计算机科学和信息技术中非常重要,因为它为数据提供了一个独特的时间参考。时间戳通常用于记录事件发生的确切时间,是一种时间记录的方式,可以用来验证数据在某个特定时间点之前已经存在。

因此时间戳是每时每刻都在不断变化的,所以他就是一个很好的随机数。

C语言中的时间戳,指针直接填空指针即可

 

我们看到现在生成的随机数就在不断的变化,同时我们也发现了一个新的问题,如果快速的输入1,生成的数字就是一样的,这倒也不难理解,因为连续输入1的间隙小于1秒的话,时间戳不变,所以生成的数字是一样的。所以我们就可以把srand放到主函数里,只初始化一次随机数的种子,这样就可以保证每次调用game()的时候,生成的随机数都是不一样的。

具体解释:

伪随机数生成器的工作原理

// 内部有一个"当前状态"变量
static unsigned long next = 1;  // 这是内部状态// srand() 的作用:重置这个状态
void srand(unsigned int seed) {next = seed;
}// rand() 的作用:基于当前状态计算下一个数,并更新状态
int rand() {next = next * 1103515245 + 12345;  // 更新状态return (next / 65536) % 32768;     // 返回随机数
}

情况1:每次调用都srand() - 会产生相同随机数

时间: 14:30:25 (时间戳: 1642334625)
用户按1 -> game() -> srand(1642334625) -> 状态重置为1642334625 -> rand() -> 返回12345

时间: 14:30:25 (还是同一秒!时间戳: 1642334625)  
用户又按1 -> game() -> srand(1642334625) -> 状态又重置为1642334625 -> rand() -> 又返回12345

情况2:只在开始时srand() - 会产生不同随机数

程序启动: 14:30:25
main() -> srand(1642334625) -> 状态设置为1642334625

用户按1 -> game() -> rand() -> 状态从1642334625变化 -> 返回12345
内部状态现在变成了: 1854928421

用户又按1 -> game() -> rand() -> 状态从1854928421变化 -> 返回67890
内部状态现在变成了: 794738462

始终是rand()中的next在变化!所以只初始化一次srand()就可以解决这个问题

完整代码

#include<time.h>
void menu()
{printf("*****************************\n");printf("**********猜数字游戏*********\n");printf("*1.开始游戏********0.结束游戏\n");printf("*****************************\n");
}void game()
{int guess = 0;int ret = rand()%100+1;  //0-100//printf("%d\n", ret);for (;;){printf("请猜数字>:");scanf("%d", &guess);if (guess< ret){printf("猜小了\n");}else if (guess > ret){printf("猜大了\n");}else{printf("猜对了\n");break;}}
}int main()
{srand((unsigned int)time(NULL));int input = 0;do{menu();printf("请选择>>>");scanf("%d", &input);switch (input){case 1:printf("猜数字\n");game();break;case 0:printf("退出游戏\n");break;default:printf("输入错误,请重新输入\n");break;}} while (input);
}

http://www.lryc.cn/news/591478.html

相关文章:

  • Kotlin集合与空值
  • 使用位运算优化 Vue.js 应用:高效状态管理技巧
  • 学习 Flutter (四):玩安卓项目实战 - 中
  • 【LeetCode 热题 100】230. 二叉搜索树中第 K 小的元素——中序遍历
  • Java全栈面试实录:从电商支付到AIGC的深度技术挑战
  • HTML常用标签汇总(精简版)
  • Easy ARM2132
  • 测试学习之——Pytest Day3
  • 【git】使用教程
  • HTTP 状态码笔记
  • element-plus——图标推荐
  • milvus向量数据库连接测试 和 集合维度不同搜索不到内容
  • 嵌入式时钟系统
  • C++ 返回值优化(Return Value Optimization, RVO)
  • c++列表初始化
  • MyUI轮播Carousel组件文档
  • Windows10笔记本电脑开启BIOS
  • deep learning(李宏毅)--(六)--loss
  • “显著性”(Saliency)是计算机视觉中的一个重要概念,主要指的是图像或视频中最吸引人注意力的区域或对象
  • 川翔云电脑:云端算力新标杆,创作自由无边界
  • 产品经理如何绘制流程图
  • 4.PCL点云的数据结构
  • 上证50etf期权交易限制的是什么?
  • 【JAVA新特性】Java 8 新特性实战
  • 小程序性能优化全攻略:提升用户体验的关键策略
  • Java List 集合详解:从基础到实战,掌握 Java 列表操作全貌
  • Kubernetes 学习笔记
  • 【JEECG 组件扩展】JSwitch开关组件扩展单个多选框样式
  • 基于pytorch深度学习笔记:1.LeNetAlexNet
  • XXE漏洞4-XXE无回显文件读取-PentesterLab靶场搭建