分支和循环语句

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

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

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

什么是语句?

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

分支语句(选择结构)

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

if 语句

if语句的语法结构(如果表达式的结果为真,则语句执行)

cpp 复制代码
语法结构:
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****书写形式的对比

cpp 复制代码
//代码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 语句。

cpp 复制代码
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。

练习

这段代码输出什么?

cpp 复制代码
#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语句:

cpp 复制代码
if(条件)
     语句;

当条件满足的情况下,if语句后的语句执行,否则不执行。但是这个语句只会执行一次。
但是我们发现生活中很多的实际的例子是:同一件事情我们需要完成很多次。 那我们怎么做呢? C语言中给我们引入了:while语句,可以实现循环。

cpp 复制代码
//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

答案:

我们看到
break在while循环中的作用:
其实在循环中只要遇到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中有这段逻辑:

cpp 复制代码
if (ch < '0' || ch > '9')
{
    continue;
}
putchar(ch);

当输入字符时:

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

代码2的行为分析

代码2没有过滤逻辑:

cpp 复制代码
while ((ch = getchar()) != EOF)
{
    putchar(ch);
}

当输入字符时:

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

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

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

for循环

我们已经知道了while循环,但是我们为什么还要一个for循环呢? 首先来看看for循环的语法:

cpp 复制代码
for(表达式1;表达式2;表达式3)
 循环语句;

表达式****1表达式1为初始化部分,用于初始化循环变量的。 表达式****2表达式2为条件判断部分,用于判断循环时候终止。 表达式****3表达式3为调整部分,用于循环条件的调整。
eg:打印1-10

for循环的执行流程图:

现在我们对比一下for循环和while循环:

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

breakcontinuefor****循环中

我们发现在for循环中也可以出现break和continue,他们的意义和在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****语句的语法
cpp 复制代码
do
 循环语句;
while(表达式);

执行流程

do****语句的特点

循环至少执行一次,使用的场景有限,所以不是经常使用

do while循环中的breakcontinue

do while和while 中的break与continue一致

break直接跳出循环

continue后一致无限循环

练习

  1. 计算 n的阶乘。

但是因为int是有范围的,严格意义上的n的阶乘是不能这么来写的,下面这段代码是有局限性的

  1. 计算 1!+2!+3!+......+10!

    简化版代码
  2. 在一个有序数组中查找具体的某个数字n。 编写int binsearch(int x, int v[], int n); 功能:在v[0]
    <=v[1]<=v[2]<= ....<=v[n-1]的数组中查找x.
    最为简单的遍历方法

    二分查找
  3. 编写代码,演示多个字符从两端移动,向中间汇聚。
  4. 编写代码实现,模拟用户登录情景,并且只能登录三次。(只允许输入三次密码,如果密码正确则提示登录成,如果三次均输入错误,则退出程序。

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

goto语句

C语言中提供了可以随意滥用的 goto语句和标记跳转的标号。
从理论上 goto语句是没有必要的,实践中没有goto语句也可以很容易的写出代码。

一个简单的goto程序,打印一个hehe,goto在跳转到again,然后在打印hehe,最终程序进入了死循环。 而且这个代码我们完全可以用for来进行代替,所以goto是可有可无的。

但是某些场合下goto语句还是用得着的,最常见的用法就是终止程序在某些深度嵌套的结构的处理过程,例如一次跳出两层或多层循环。
这种情况使用break是达不到目的的。它只能从最内层循环退出到上一层的循环。
比如说下面这种情况,我有三层循环,我想直接从最内层的循环中跳出去,就可以使用goto语句,break只能跳出当前循环。

cpp 复制代码
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()的时候,生成的随机数都是不一样的。

具体解释:

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

cpp 复制代码
// 内部有一个"当前状态"变量
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()就可以解决这个问题

完整代码

cpp 复制代码
#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);
}
相关推荐
阑梦清川6 分钟前
高精度乘法模版代码思路分析(C++版本)
算法
Ashlee_code18 分钟前
裂变时刻:全球关税重构下的券商交易系统跃迁路线图(2025-2027)
java·大数据·数据结构·python·云原生·区块链·perl
#molecule43 分钟前
指针与数组、字符和函数之间的关系
算法
wh_xia_jun1 小时前
K-means 聚类在肺炎患者分型中的应用(简单示例)
算法·kmeans·聚类
闻缺陷则喜何志丹1 小时前
【带权的并集查找】 P9235 [蓝桥杯 2023 省 A] 网络稳定性|省选-
数据结构·c++·蓝桥杯·洛谷·并集查找
jie*1 小时前
python(one day)——春水碧于天,画船听雨眠。
开发语言·数据结构·python·算法·线性回归
321Leo1232 小时前
抖音“读心术”全解析:我们为何在短视频的世界里欲罢不能?
人工智能·算法
刚入坑的新人编程3 小时前
暑期算法训练.3
c++·算法
草莓熊Lotso3 小时前
【LeetCode刷题指南】--数组串联,合并两个有序数组,删除有序数组中的重复项
c语言·数据结构·其他·刷题
平哥努力学习ing3 小时前
C语言内存函数
c语言·开发语言·算法