Bootstrap

【C语言】分⽀和循环

1. if语句

悬空else问题

如果有多个 if 和 else ,== else 总是跟最接近的 if 匹配==。
我们⾸先从下⾯代码开始

 #include <stdio.h>
 int main()
 {
 int a = 0;
 int b = 2;
 if(a == 1)
 if(b == 2)
 printf("hehe\n");
 else
 printf("haha\n");
 return 0;
 }

程序运⾏的结果是啥?
很多上来就判断打印 haha 但是当运⾏代码,啥都不输出。
为什么呢?
这就是悬空 else 的问题,如果有多个 if 和 else , else 总是跟最接近的 if 匹配。上⾯的代码else 和第⼀个 if 语句对⻬,让我们以为 else 是和第⼀个if匹配的,当 if语句不成⽴的时候,⾃然想到的就是执⾏ else ⼦句打印 haha ,但实际上 如果第⼀个 if 语句就不成⽴,嵌套 if 和 else 就没机会执⾏了,最终啥都不打印。
只要带上适当的⼤括号,代码的逻辑就会更加的清晰,所以⼤家以后在写代码的时候要注意括号的使⽤,让代码的可读性更⾼。

2. 关系操作符

关系表达式通常返回 0 或 1 ,表⽰真假。C 语⾔中, 0 表⽰假,所有⾮零值表⽰真。⽐如, 20 > 12 返回 1 , 12 > 20 返回 0 。关系表达式常⽤于 if 或 while 结构。
注意:相等运算符 == 与赋值运算符 = 是两个不⼀样的运算符,不要混淆。有时候,可能会不⼩⼼写出下⾯的代码,它可以运⾏,但很容易出现意料之外的结果。if (x = 3) ...上⾯⽰例中,原意是 x == 3 ,但是不⼩⼼写成 x = 3 。这个式⼦表⽰对变量 x 赋值 3 ,它的返回值为 3 ,所以 if 判断总是为真。为了防⽌出现这种错误,有的程序员喜欢将变量写在等号的右边 if (3 == x) ... 这样的话,如果把 == 误写成 = ,编译器就会报错。
另⼀个需要避免的错误是:多个关系运算符不宜连⽤。i < j < k中,连续使⽤两个⼩于运算符。这是合法表达式,不会报错,但是通常达不到想要的结果。因为关系运算符是从左到右计算,所以实际执⾏的是下⾯的表达式。(i < j) < k 上式中, i < j 返回 0 或 1 ,所以最终是 0 或 1 与变量 k 进⾏⽐较。如果想要判断变量 j是否在 i 和 k 之间,应该使⽤下⾯的写法。i < j && j < k

3. 条件操作符

条件操作符也叫三⽬操作符,需要接受三个操作数的,形式:exp1 ? exp2 : exp3
条件操作符的计算逻辑是:如果 exp1 为真, exp2 计算;如果exp1 为假, exp3 计算,计算的结果是整个表达式的结果。

4. 逻辑操作符:&& , || , !

C语⾔逻辑运算符还有⼀个特点,它总是先对左侧的表达式求值,再对右边的表达式求值,这个顺序是保证的。如果左边的表达式满⾜逻辑运算符的条件,就不再对右边的表达式求值。这种情况称为“短路”。
在这里插入图片描述
a++即0 &&的结果一定为0 后边++b和d++不用算 所以abcd分别是1234

5. switch语句

switch 语句是⼀种特殊形式的 if…else 结构,⽤于判断条件有多个结果的情况。它把多重的 else if 改成更易⽤、可读性更好的形式。

switch (expression) {
 case value1: statement
 case value2: statement
 default: statement
}

上⾯代码中,根据表达式 expression 不同的值,执⾏相应的 case 分⽀。如果找不到对应的值,就执⾏ default 分⽀。
注:
• switch 后的 expression 必须是整型表达式
• case 后的值,必须是整形常量表达式

5.1 if语句和switch语句的对⽐

练习:输⼊任意⼀个整数值,计算除3之后的余数
如果使⽤if语句完成,如下:

#include <stdio.h>
int main()
{
 int n = 0;
 scanf("%d", &n);
 if(n%3 == 0)
 printf("整除,余数为0\n");
 else if(n%3 == 1)
 printf("余数是1\n");
 else
 printf("余数是2\n");
 return 0;
}

如果使⽤switch语句改写,就可以是这样的:

#include <stdio.h>
int main()
{
 int n = 0;
 scanf("%d", &n);
 switch(n%3)
 {
 case 0:
 printf("整除,余数为0\n"); 
 break;
 case 1:
 printf("余数是1\n"); 
 break;
 case 2:
 printf("余数是2\n"); 
 break;
 }
 return 0;
}

上述的代码中,我们要注意的点有:

  1. case 和后边的数字之间必须有空格
  2. 每⼀个 case 语句中的代码执⾏完成后,需要加上 break ,才能跳出这个switch语句。

5.2 switch语句中的break

前⾯的代码中,如果我们去掉case语句中的break,会出现什么情况呢?
在这里插入图片描述
我们发现,7除以3本来余数是1,但是我们发现程序运⾏的结果多了⼀⾏“余数是2”的打印。
这是为什么呢?
原因是 switch 语句也是分⽀效果的,只有在 switch 语句中使⽤ break 才能在跳出 switch 语句,如果某⼀个 case 语句的后边没有 break 语句,代码会继续往下执⾏,有可能执⾏其他 case语句中的代码,直到遇到 break 语句或者 switch 语句结束。所以在 switch 语句中 break 语句是⾮常重要的,能实现真正的分⽀效果。当然, break 也不是每个 case 语句都得有,这就得根据实际情况来看了。
练习:
输⼊⼀个1~7的数字,打印对应的星期⼏
输⼊:1 输出:星期⼀
输⼊:2 输出:星期⼆
输⼊:3 输出:星期三
输⼊:4 输出:星期四
输⼊:5 输出:星期五
输⼊:6 输出:星期六
输⼊:7 输出:星期天

#include <stdio.h>
int main()
{
 int day = 0;
 scanf("%d", &day);
 switch(day)
 {
 case 1printf("星期⼀\n");
 break;
 case 2:
 printf("星期⼆\n");
 break;
 case 3:
 printf("星期三\n");
 break; 
 case 4:
 printf("星期四\n");
 break; 
 case 5:
 printf("星期五\n");
 break;
 case 6:
 printf("星期六\n");
 break;
 case 7:
 printf("星期天\n"); 
 break;
 }
 return 0;
}

如果需求发⽣变化,变为:

  1. 输⼊1~5,输出的是“⼯作⽇”;
  2. 输⼊6~7,输出“休息⽇”
#include <stdio.h>
int main()
{
 int day = 0;
 scanf("%d", &day);
 switch(day)
 {
 case 1case 2:
 case 3:
 case 4:
 case 5:
 printf("⼯作⽇\n");
 break;
 case 6:
 case 7:
 printf("休息⽇\n");
 break;
 }
 return 0;
}

上⾯的练习中,我们发现应该根据实际的情况,来在代码中觉得是否使⽤ break ,或者在哪⾥使⽤break ,才能正确完成实际的需求。

5.3 switch语句中的default

在使⽤ switch 语句的时候,我们经常可能遇到⼀种情况,⽐如 switch 后的表达式中的值⽆法匹配代码中的 case 语句的时候,这时候要不就不做处理,要不就得在 switch 语句中加⼊
default ⼦句。

switch (expression) {
 case value1: statement
 case value2: statement
 default: statement
}

switch 后边的 expression 的结果不是 value1 也不是 value2 时,就会执⾏default ⼦句。就⽐如前⾯做的打印星期的练习,如果 day 的输⼊不是1~7的值,我们要提⽰:输⼊错误,则可以这样完成代码:

#include <stdio.h>
int main()
{
int day = 0;
 scanf("%d", &day);
 switch(day)
 {
 case 1case 2:
 case 3:
 case 4:
 case 5:
 printf("⼯作⽇\n");
 break;
 case 6:
 case 7:
 printf("休息⽇\n");
 break;
 default:
 printf("输⼊错误\n");
 break;
 }
 return 0;
}

5.4 switch语句中的case和default的顺序问题

在 switch 语句中 case ⼦句和 default ⼦句有要求顺序吗? default 只能放在最后吗?
其实,在 switch 语句中 case 语句和 default 语句是没有顺序要求的,只要你的顺序是满⾜实际需求的就可以。不过我们通常是把 default ⼦句放在最后处理的。

6. while循环

⾸先上来就是执⾏判断表达式,表达式的值为0,循环直接结束;表达式的值不为0,则执⾏循环语句,语句执⾏完后再继续判断,是否进⾏下⼀次判断
if 和 while的对⽐

#include <stdio.h>
int main()
{
 if(1)
 printf("hehe\n"); //if后边条件满⾜,打印⼀次hehe
 return 0;
}
#include <stdio.h>
int main()
{
 while(1)
 printf("hehe\n"); //while后边的条件满⾜,死循环的打印hehe
 return 0;
}

这就是他们的区别,while语句是可以实现循环效果的。

7. for循环

7.1 语法形式

for 循环是三种循环中使⽤最多的, for 循环的语法形式如下:

for(表达式1; 表达式2; 表达式3)
 语句;//如果循环体想包含更多的语句,可以加上⼤括号

表达式1 ⽤于循环变量的初始化
表达式2 ⽤于循环结束条件的判断
表达式3 ⽤于循环变量的调整

7.2 while循环和for循环的对⽐

在这里插入图片描述
for 和 while 在实现循环的过程中都有初始化、判断、调整这三个部分,但是 for 循环的三个部分⾮常集中,便于代码的维护,⽽如果代码较多的时候 while 循环的三个部分就⽐较分散,所以从形式上 for 循环要更优⼀些。

8. do-while循环

在循环语句中 do while 语句的使⽤最少,它的语法如下:

do
 语句;
while(表达式)

while 和 for 这两种循环都是先判断,条件如果满⾜就进⼊循环,执⾏循环语句,如果不满⾜就跳出循环;⽽ do while 循环则是先直接进⼊循环体,执⾏循环语句,然后再执⾏ while 后的判断表达式,表达式为真,就会进⾏下⼀次,表达式为假,则不再继续循环。
⼀般 do while 使⽤在循环体⾄少被执⾏⼀次的场景下,所以较少⼀些。

9. break和continue语句

在循环执⾏的过程中,如果某些状况发⽣的时候,需要提前终⽌循环,这是⾮常常⻅的现象。C语⾔中提供了 break 和 continue 两个关键字,就是应⽤到循环中的。
• break 的作⽤是⽤于永久的终⽌循环,只要 break 被执⾏,直接就会跳出循环,继续往后执⾏。
• continue 的作⽤是跳过本次循环中 continue 后边的代码,在 for 循环和 while 循环中有所差异的。

10. 循环的嵌套

练习
找出100~200之间的素数,并打印在屏幕上。

  1. 要从100 ~ 200之间找出素数,⾸先得有100~200之间的数,这⾥可以使⽤循环解决。
  2. 假设要判断i是否为素数,需要拿2 ~ i-1之间的数字去试除i,需要产⽣2~i-1之间的数字,也可以使⽤循环解决。
  3. 如果2~i-1之间有数字能整除i,则i不是素数,如果都不能整除,则i是素数。
    但是注意:任意一个数可以写成m=a*b方式时a和b中至少有一个数小于等于m^1/2
    所以试除时没必要到m-1,到m ^ 1/2即可
#include <stdio.h>
int main()
{
 int i = 0;
 //循环产⽣100~200的数字
	for(i=100; i<=200; i++)
 	{//判断i是否为素数 循环产⽣2~i-1之间的数字
	 	int j = 0;
	 	int flag = 1;//假设i是素数
		for(j=2; j<=sqrt(i); j++)
		{
			if(i % j == 0)
			{
				flag = 0;
				break;
			}
		}
		if(flag == 1)
			printf("%d ", i);
	}
	return 0;
}

11. goto语句

C语⾔提供了⼀种⾮常特别的语法,就是 goto 语句和跳转标号, goto 语句可以实现在同⼀个函数
内跳转到设置好的标号处。
例如:

#include <stdio.h>
int main()
{
 	printf("hehe\n");
 	goto next;
 	printf("haha\n");
next:
 	printf("跳过了haha的打印\n"); 
 	return 0;
}

goto 语句如果使⽤的不当,就会导致在函数内部随意乱跳转,打乱程序的执⾏流程,所以我们的建
议是能不⽤尽量不去使⽤;但是 goto 语句也不是⼀⽆是处,在多层循环的代码中,如果想快速跳出
使⽤ goto 就⾮常的⽅便了。

for(...)
{
 	for(...)
 	{
 		for(...)
 		{
 		if(disaster)
 		goto error;
 		}
 	}
}
error:
 //...

本来 for 循环想提前退出得使⽤ break ,如果3层循环嵌套就得使⽤3个 break 才能跳出循环,所以在这种情况下使⽤ goto 语句就会更加的快捷。

写一个关机程序
在这里插入图片描述

int main()  
{  
	char input [20] = { 0 };  
	//关机  
	system("shutdown -s -t 60");  
again:  
	printf("请注意,你的电脑在1分钟内关机,如果输入:我是猪,就取消关机\n");  
	//输入  
	scanf("%s" input);  
	//判断输入信息的正确性  
	//两个字符串比较相等不能使用==,应该使用strcmp函数  
	if (strcmp(input, "我是猪") == 0)  
	{  
		system("shutdown -a");//取消关机  
	}  
	else  
	{  
		goto again;  
	}  
	return 0;  
}

也可以用while循环

int main()  
{  
	char input[20] = { 0 };  
	//关机  
	system("shutdown -s -t 60");  
	while (1)  
	{  
		printf("请注意,你的电脑在1分钟内关机,如果输入:我是猪,就取消关机\n");  
		//输入  
		scanf("%s", input);  
		//判断输入信息的正确性  
		//两个字符串比较相等不能使用==,应该使用strcmp函数  
		if (strcmp(input, "我是猪") == 0)  
		{  
			system("shutdown -a"); //取消关机  
			break;  
		}  
	}  
	return 0;  
}

12.写⼀个猜数字游戏

游戏要求:

  1. 电脑⾃动⽣成1~100的随机数
  2. 玩家猜数字,猜数字的过程中,根据猜测数据的⼤⼩给出⼤了或⼩了的反馈,直到猜对,游戏结束

12.1随机数生成

要想完成猜数字游戏,⾸先得产⽣随机数,那怎么产⽣随机数呢?

1.1 rand

C语⾔提供了⼀个函数叫 rand,这函数是可以⽣成随机数的,函数原型: int rand (void);
rand函数会返回⼀个伪随机数,这个随机数的范围是在0~RAND_MAX之间,这个RAND_MAX的⼤⼩是依赖编译器上实现的,但是⼤部分编译器上是32767。
rand函数的使⽤需要包含⼀个头⽂件是:stdlib.h
虽然⼀次运⾏中产⽣的5个数字是相对随机的,但是下⼀次运⾏程序⽣成的结果和上⼀次
⼀模⼀样,这就说明有点问题。其实rand函数⽣成的随机数是伪随机的,伪随机数不是真正的随机数,是通过某种算法⽣成的随机数。真正的随机数的是⽆法预测下⼀个值是多少的。⽽rand函数是对⼀个叫“种⼦”的基准值进⾏运算⽣成的随机数。之所以前⾯每次运⾏程序产⽣的随机数序列是⼀样的,那是因为rand函数⽣成随机数的默认种⼦是1。如果要⽣成不同的随机数,就要让种⼦是变化的。

1.2 srand

C语⾔中⼜提供了⼀个函数叫 srand,⽤来初始化随机数的⽣成器的,srand的原型:void srand (unsigned int seed);
程序中在调⽤ rand 函数之前先调⽤ srand 函数,通过 srand 函数的参数seed来设置rand函数⽣成随机数的时候的种⼦,只要种⼦在变化,每次⽣成的随机数序列也就变化起来了。
也就是说给srand的种⼦是如果是随机的,rand就能⽣成随机数;在⽣成随机数的时候⼜需要⼀个随机数,这就⽭盾了。

1.3 time

在程序中我们⼀般是使⽤程序运⾏的时间作为种⼦的,因为时间时刻在发⽣变化的。
在C语⾔中有⼀个函数叫 time ,就可以获得这个时间,time函数原型:time_t time (time_t* timer);
time 函数会返回当前的⽇历时间,其实返回的是1970年1⽉1⽇0时0分0秒到现在程序运⾏时间之间的差值,单位是秒。返回的类型是time_t类型的,time_t 类型本质上其实就是32位或者64位的整型类型。
time函数的参数 timer 如果是⾮NULL的指针的话,函数也会将这个返回的差值放在timer指向的内存中带回去。如果 timer 是NULL,就只返回这个时间的差值。time函数返回的这个时间差也被叫做:时间戳。
time函数的时候需要包含头⽂件:time.h

//VS2022 上time_t类型的说明
#ifndef _CRT_NO_TIME_T
 #ifdef _USE_32BIT_TIME_T
 typedef __time32_t time_t;
 #else
 typedef __time64_t time_t;
 #endif
#endif
typedef long __time32_t;
typedef __int64 __time64_t;

如果只是让time函数返回时间戳,我们就可以这样写:time(NULL);//调⽤time函数返回时间戳,这⾥没有接收返回值
那我们就可以让⽣成随机数的代码改写成如下:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
 //使⽤time函数的返回值设置种⼦
 //因为srand的参数是unsigned int类型,我们将time函数的返回值强制类型转换
 srand((unsigned int)time(NULL));
 printf("%d\n", rand());
 printf("%d\n", rand());
 printf("%d\n", rand());
 return 0;
}

srand函数是不需要频繁调⽤的,⼀次运⾏的程序中调⽤⼀次就够了。

1.4 设置随机数的范围

如果我们要⽣成0~99之间的随机数,⽅法如下:
rand() % 100;//余数的范围是0~99
如果要⽣成1~100之间的随机数,⽅法如下:
rand()%100+1;//%100的余数是0~99,0~99的数字+1,范围是1~100
如果要⽣成100~200的随机数,⽅法如下:
100 + rand()%(200-100+1) //余数的范围是0~100,加100后就是100~200
所以如果要⽣成a~b的随机数,⽅法如下:
a + rand()%(b-a+1)

12.2 猜数字游戏实现

参考代码:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void game()
{
	 int r = rand()%100+1;
	 int guess= 0;
	 while(1)
	 {
		 printf("请猜数字>:");
		 scanf("%d", &guess);
		 if(guess < r)
		 	printf("猜⼩了\n");
		 else if(guess > r)
		 	printf("猜⼤了\n");
		 else
		 {
		 	printf("恭喜你,猜对了\n");
		 	break;
		 }
	 } 
}
void menu()
{
	 printf("***********************\n");
	 printf("****** 1. play ******\n");
	 printf("****** 0. exit ******\n");
	 printf("***********************\n");
}
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;
}

还可以加上猜数字的次数限制,如果5次猜不出来,就算失败.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void game()
{
	 int r = rand() % 100 + 1;
	 int guess = 0;
	 int count = 5;
	 while (count)
	 {
		 printf("\n你还有%d次机会\n", count);
		 printf("请猜数字>:");
		 scanf("%d", &guess);
		 if (guess < r)
		 	printf("猜⼩了\n");
		 else if (guess > r)
		 	printf("猜⼤了\n");
		 else
		 {
		 	printf("恭喜你,猜对了\n");
		 	break;
		 }
		 count--;
	 }
	 if (count == 0)
	 	printf("你失败了,正确值是:%d\n", r);
 }
void menu()
{
	 printf("***********************\n");
	 printf("****** 1. play ******\n");
	 printf("****** 0. exit ******\n");
	 printf("***********************\n");
}
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;
}
;