C语言复习第2章 分支和循环

一、介绍

1.1 有哪些分支(选择)和循环语句

  • 分支:if;switch
  • 循环:while;for;do…while
  • goto语句

1.2 C语言的结构化体现在哪?

  • 任何一种事情 都可以由这三种结构排列组合而成
  • 这是一种高度抽象
    在这里插入图片描述

1.3 C语言有哪些语句类型

  1. 2+3;
  2. Add(2,3);
  3. 控制语句用于控制程序的执行流程,以实现程序的各种结构方式 :顺序结构,选择结构,循环结构
    在这里插入图片描述
  • C语言有九种控制语句:
    在这里插入图片描述

二、分支语句if(选择结构)

2.1 if-if-if和if-elif-el的区别

  • if-if 两段if都要执行 都要进行判断 ( )里的判断关系是或者||关系

  • if elseif elseif 看做整体 只有一个入口

  • if elif elif elif … else 是一个整体 也只有一个入口

  • 最外面的两个if都要判断 因为闰年本来就有两种可能的情况 这样写没问题
    在这里插入图片描述

  • 这种写法 只有一个入口 也很合逻辑 所以( )里的条件肯定是互斥的
    在这里插入图片描述

  • 这就是一个错误

  • 我的逻辑肯定是只有一个入口的 因为这是单值函数

  • 但是这么写有可能会进去两次 y的值被改了两次
    在这里插入图片描述

2.2 擅用if嵌套

if(是人)
{
//比如在判断一个人是不是成年之前 我得确保他是个人 才进来判断
if-else if-else
}

2.3 擅用{ }和缩进!!!

  • 这坨狗屎代码 风格很差! 虽然没错!

  • 注意:下面蓝色的if else整体可以看作一条语句(因为要么执行if的hehe 要么执行else的haha 最终只执行一条语句的) 所以可以不用加上大括号

  • 可以不加就不加了么 加上去多清晰啊

  • 这段代码什么都不输出 因为a == 1为假 不进去了
    在这里插入图片描述

  • 既然意思是一样的 这样写多好
    在这里插入图片描述

2.4 悬空else和最近的if匹配

  • 就算故意这么缩进 else也是和最近的一个if匹配的
  • else就近匹配 和离他最近的 还未匹配的if匹配
    在这里插入图片描述
  • 代码风格很重要 这种代码一眼就看出谁和谁匹配
    在这里插入图片描述

2.5 return之后程序就结束了

  • 1和2本质上是一样的 都表示:条件成立就return x 否则就return y
  • 因为return之后程序就不执行了
  • 当然如果这里不是return的话 那就不一样了
    代码1就不是非此即彼的关系了 第二句一定会执行
  • 建议写成代码2这种风格 多写点没事 要一目了然
//代码1
if (condition) {
    return x;
}
return y;

//代码2
if(condition)
{
    return x;
}
else
{
    return y;
}

2.6 if里判断变量==常量比较的时候 建议把常量放在左边

如果我这样不小心写错了 编译器可以成功编译 不能发现问题
在这里插入图片描述
如果我这么写 就编译失败 容易发现错误

在这里插入图片描述

  • 如果是表达式的话 就无所谓了 左边右边都行 因为表达式本身就不可以被赋值
    在这里插入图片描述

2.7 再次理解一下if elseif的逻辑

在后面写判断闰年的时候 我们很明确:闰年有两种判断方法 应该是或者的关系
给你一个年份 只要满足两种规则的其一 他就是闰年 所以我们说 肯定是if if都判断 而不能是if elseif
但是实际上还是要具体去理解整体代码的逻辑的 比如下图 虽然是if elseif 但是逻辑并没有错
在这里插入图片描述

下面是五种写法 可以都看看 思考思考逻辑
下图是运行结果 可以看出函数5把2000年漏掉了
5这种写法才必须是if if的 因为2000一上去只进入第一个if 不会再去尝试else if了

总结来说 也没这么复杂
只要(year % 400 == 0) 或者(year % 4 == 0) && (year % 100 != 0) 这两个规则都可以被试到就行了

在这里插入图片描述

int isLeapYear1(int year)
{
	return (year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0));
}

int isLeapYear2(int year)
{
	if ((year % 4 == 0) && (year % 100 != 0))
		return 1;
	//这里也可以写成if if
	else if (year % 400 == 0)
		return 1;
	return 0;
}

int isLeapYear3(int year)
{
	if ((year % 4 == 0) && (year % 100 != 0))
		return 1;
	if (year % 400 == 0)
		return 1;
	return 0;
}

int isLeapYear4(int year)
{
	if (year % 4 == 0)
	{
		if (year % 100 != 0)
			return 1;
	}
	//这里必须写if 不能是else if
	if (year % 400 == 0)
		return 1;
	return 0;
}

int isLeapYear5(int year)
{
	if (year % 4 == 0)
	{
		if (year % 100 != 0)
			return 1;
	}
	//这里必须写if 不能是else if
	else if (year % 400 == 0)
		return 1;
	return 0;
}

int main()
{
	int i = 0;
	for (i = 1949; i <= 2024; i++)
	{
		if (isLeapYear1(i))
			printf("%d ", i);		
	}
	printf("=================\n");

	for (i = 1949; i <= 2024; i++)
	{
		if (isLeapYear2(i))
			printf("%d ", i);
	}
	printf("=================\n");

	for (i = 1949; i <= 2024; i++)
	{
		if (isLeapYear3(i))
			printf("%d ", i);
	}
	printf("=================\n");

	for (i = 1949; i <= 2024; i++)
	{
		if (isLeapYear4(i))
			printf("%d ", i);
	}
	printf("=================\n");

	for (i = 1949; i <= 2024; i++)
	{
		if (isLeapYear5(i))
			printf("%d ", i);
	}
	printf("=================\n");

	return 0;
}

三、分支语句switch(选择结构)

3.1 使用场景

在这里插入图片描述
假如要判断是星期几 用if很繁琐

在这里插入图片描述

3.2 注意穿透现象

  • 没有break 会发生穿透 要灵活运用break
  • case决定入口;break决定出口(不再继续执行下一个case 跳出当前switch)
  • 但是这个 “穿透” 并不一定就是坏的 这只是一种现象 下图反而利用了穿透
  • 多个case语句执行同一条语句 就可以利用穿透
    在这里插入图片描述

3.3 default不是必须放在最后的

  • default放在最后 他的break才能省略
  • 其实default也是一个入口 表示"其他情况的入口"
    在这里插入图片描述

3.4 switch+do…while的一种经典用法

如果输入0
break到switch外面
继续执行while(input) 发现为假 则出循环
在这里插入图片描述

3.5 switch( )的括号里必须是整型表达式

  • switch(必须是整型表达式) 可以是变量 也可以是常量
  • 枚举的值也可以 枚举是枚举类型 但是枚举的值肯定是整型
    在这里插入图片描述

3.6 case后面必须跟整型常量表达式

case跟1;2;1+1 都行 但不可以是变量n 常变量也不行
在这里插入图片描述

  • 字面常量ok
  • #define定义的标识符常量ok
  • 枚举常量ok
  • const修饰的常变量NO(因为本质还是个变量 只是具备常量不可修改的属性)
    在这里插入图片描述

3.7 switch可以嵌套使用 break只会跳出当前一层的switch

  • break只会跳出当前的switch分支 只跳一层
  • 第一个break执行 只跳出内层switch 继续执行外层switch的case 4
  • 答案:m=5 n=3
    在这里插入图片描述

3.8 switch里面没有continue 只有break

  • break可以说是跳出当前一层的switch 不再继续执行下一个case了
  • continue只有循环里会有 跳过当次循环 继续下一次

3.9 break的两种语法

  1. 跳出当前switch分支 只跳一层
  2. 跳出当前的整个循环(continue只跳过当次循环)

所以循环里有switch 不要搞混淆了
这个break只跳出当前的switch 然后会继续执行循环判断条件
在这里插入图片描述

四、循环语句

4.1 有三种循环语句

  • while
  • for
  • do while

4.2 while的执行流程

  • continue是跳到判断部分去了
    在这里插入图片描述

4.3 break和continue

  • continue就是跳过本次循环 直接跳到循环判断部分
    在这里插入图片描述

下面的代码会死循环 打印1 2 3 4然后死循环 我又写成1 2 3 4 6 7 8 9 10了
continue跳过本次循环 当n=5的时候 再也没机会自增了!!! 就死循环了!!
在这里插入图片描述

做这三个地方的修改 才是我想的打印1~10 除了5
在这里插入图片描述

  • break是直接把当前这一层的循环结束了
    在这里插入图片描述

4.4 for循环

  • for循环弥补了while循环三个必要条件太分散的缺点
    在这里插入图片描述
  • continue是跳到调整部分去了
    在这里插入图片描述

4.5 for和while的对比

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

int i = 0;
//实现相同的功能,使用while
i=1;//1.初始化部分
while(i<=10)//2.判断部分
{
 	printf("hehe\n");
	 i = i+1;//3.调整部分
}
//实现相同的功能,使用while
for(i=1; i<=10; i++)
{
	 printf("hehe\n");
}

4.6 continue在while和for中的差异

  • break都是终止循环 在while和for里都很好理解 没什么差异
  • continue虽然都是跳过本次循环 但有所差异
  • 下图也能进一步说明 for循环更好!

在for循环中 continue跳到调整部分 不容易死循环
下图打印1 2 3 4 6 7 8 9 10 不会死循环 因为每次continue 会跳到i++在这里插入图片描述

在while/do while循环 continue跳到判断部分 小心死循环
在这里插入图片描述

4.7 for循环的变种

  • 判断部分如果被省略 就是死循环
    在这里插入图片描述

4.8 可以使用多个变量控制循环

  • 下图打印两个hehe
  • 多个变量之间用逗号隔开
    在这里插入图片描述

4.9 do while

  • 就一个注意点 do while的特点很明确 他至少会执行一次

  • stmt是循环体 expr是判断表达式
    在这里插入图片描述

  • 他和while一样 continue会跳到判断部分

  • 故下图代码打印1 2 3 4 5之后会死循环的打印5(看清楚 这里5也会打印哦~)
    在这里插入图片描述

4.10 建议在循环体外定义变量 在循环体内赋值

  • 循环里定义的变量 不会有重定义报错

  • 建议就在循环外面int flag; 然后在循环里面flag=1;
    在这里插入图片描述

  • 不建议像下图这么写 只是理解一下为什么不报错(我曾经感觉这是重定义行为)
    在这里插入图片描述

五、经典案例:猜数字游戏

RAND_MAX

  • 也就是32767
    在这里插入图片描述
    在这里插入图片描述

time_t

  • 所以time_t的本质就是long long
    在这里插入图片描述
    在这里插入图片描述

NULL空指针

  • 他的本质其实是0
    在这里插入图片描述
    在这里插入图片描述

rand()函数的问题-stdlib.h

头文件是stdlib.h在这里插入图片描述

问题在于 第一次执行程序 他生产的随机数 和我第二次运行生成的随机数 是一模一样的
其实rand函数并不是这么用的
在这里插入图片描述
在这里插入图片描述

srand()设置随机数种子-stdlib.h

来看一下rand()函数的介绍
翻译:这个数字是由算法生成的,每次调用它都会返回一个明显不相关的数字序列。该算法使用种子来生成序列,并使用srand函数将其初始化为特定的值。
在这里插入图片描述

大致意思就是rand()使用之前 必须先调用一下srand()
在这里插入图片描述
在这里插入图片描述

  • 但是又存在一个问题 如果我给srand()的参数是一个固定的0 如下图 那我首先的确会生成一个随机数:38 但是我每次选择玩游戏的时候 他生成的都是38
    在这里插入图片描述

  • 经过测试 发现 我把0换成1 2 100等等 生成的随机数也会随之变化

  • 所以问题就是在于 如果我把srand()的参数写死了 对应的随机数就死了!!

  • 猜想:该怎么给srand()传一个不断变化的参数呢?---时间戳

时间戳与time()函数-time.h

  • C语言中 time()函数 会返回时间戳

  • time()需要time.h
    在这里插入图片描述

  • time()的返回值本质是long long

  • time()需要的参数类型是time_t* 一个指针—>不管他 就给他传一个空指针NULL就行 只要time()可以把时间戳返回就行了
    在这里插入图片描述

srand()在程序中只要调用一次就行

  • 如果每次选择1 玩游戏 都重新设置一下起点 会出现问题 如下图
    在这里插入图片描述

  • 因为我把srand()放在了game()里面 每次选择玩游戏 都会执行srand() 设置一下随机数种子 每次都重新设置起点

  • 而srand()在整个程序只需要调用一次就行了
    在这里插入图片描述

  • 把他放在main函数 确保只调用一次即可

在这里插入图片描述

总结

  1. 上来先用ran()生成随机数 但是发现假如第一次运行生成的随机数是1 99 8 第二次运行生成的还是1 99 8 是同一个序列
  2. 而rand()已经告诉我们 需要用到srand() 设置随机数生成器 也就是说在调用rand() 先调用srand()设置一下随机数种子
  3. 但此时又发现 如果传给srand()的参数是固定的 生成的随机数也是固定的–>所以希望传一个不断变化的数给srand() 也就是时间戳
  4. 最红写为srand((unsigned int)time(NULL)); 然后int answer = rand() % 100 - 1;就可以生成随机数了
  5. rand()调用之前 的确需要调用srand() 但是srand()调用一次即可 不要频繁调用 否则就不够随机
  • time()需要的参数是time_t* 返回值是time_t(本质是long long)所以传入NULL 只要time()把时间戳返回就行
  • srand()需要的参数是unsigned int 返回值是void 所以还要把time返回的time_t的时间戳强转一下 再传给srand

怎么设置随机数的范围

在这里插入图片描述

终于可以开始猜数字了

  • 完整参考代码:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

void menu()
{
	printf("****************************\n");
	printf("***1.play**********0.exit***\n");
	printf("****************************\n");
}

void game()
{
	//生成随机数
	int ret = rand() % 100 + 1;//1~100
	int guess = 0;
	//猜数字
	while (1)
	{
		scanf("%d", &guess);
		if (guess < ret)
		{
			printf("猜小了\n");
		}
		else if (guess > ret)
		{
			printf("猜大了\n");
		}
		else
		{
			printf("猜对了\n");
			break;
		}
		printf("再猜\n");
	}
}

int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));//注意只要设置一次随机数生成起点
	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);
	//只有input为0的时候才会跳出循环,其余情况都会进入循环
	//而且input是定义在循环外面的 所以这里才可以使用  如果定义在循环里的话 input就只能在循环体内用
}

  • 这是我后来写过的一次 并没有模块化编程
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
	int input = 0;
	int choose = 0;
	srand((unsigned int)time(NULL));//随机数种子只要生成一次
	while (1)
	{
		int answer = rand() % 100 - 1;
        //可千万不要把这个写在下面猜数字的地方了 要不然每次都生成一个随机数
		printf("**********1.play   0.exit**********\n");
		printf("请输入你的选择:\n");
		scanf("%d", &choose);
		switch (choose)
		{
		case 0:
			printf("bye~\n");
			break;
		case 1:
			while (1)
			{
				printf("输入你猜的数字:\n");
				scanf("%d", &input);
				if (answer < input)
					printf("猜大了\n");
				else if (answer > input)
					printf("猜小了\n");
				else
				{
					printf("猜对了\n");
					break;
				}
			}
			break;
		default:
			printf("输入错误!请重新选择\n");
			break;
		}
		//可能是case 1  ,case 0,  default三种情况  
		//case 0 就需要结束整个游戏 剩余的 需要继续让玩家选择
		if (choose == 0)
			break;
	}
	return 0;
}

一些离谱的错误

  • 每次猜数字之前 都生成一个随机数 这个错误很逆天
    在这里插入图片描述

六、补充:goto语句

6.1 介绍

  • C语言允许goto 让他随意跳转

  • 但是goto语句只能在同一个函数内部跳转 不能跨函数跳转
    在这里插入图片描述

  • 死循环打印hehe了
    在这里插入图片描述

6.2 goto适用场景

  • 尽量少用goto 但也不是不可以用
  • 一次跳出两层或多层循环 这种情况使用break是达不到目的的 break只能从最内层循环退出到上一层的循环
for(...)
    for(...)
   {
        for(...)
       {
            if(disaster)
                goto error;
       }
   }
    …
error:
 if(disaster)
         // 处理错误情况

6.3 shutdown

  • -s立即关机 -t给他设置关机时间
  • -a取消关机
    在这里插入图片描述

6.4 goto小案例-关机程序

在这里插入图片描述

  • system()的头文件是stdlib.h
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	char input[20] = " ";
	system("shutdown -s -t 60");
again:
	printf("输入我是猪不然关机\n");
	scanf("%s", input);
	if (strcmp(input, "我是猪") == 0)
	{
		system("shutdown -a");
	}
	else
	{
		goto again;
	}
	return 0;
}
//这里调试的时候把debug改成release

  • 其实完全可以用while语句来实现

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值