C语言初阶——2分支和循环语句(保姆级胎教)

前言

作者写本篇文章旨在将自己在学习C语言的知识点和心得分享给大家。由于作者本人还是一个刚入门的菜鸟,不可避免的会出现一些错误和观点片面的地方,非常感谢读者指正!希望大家能一同进步,成为大牛,拿到好offer。
本系列(初识C语言)只是对C语言的基础知识点过一遍,是为了能让读者和自己对C有一个初步了解。


日志

2024.4.16忘记啥时候写的了,就当今天吧
2024.8.19第一次修改


1.什么是语句?

1.1语句

c语言中由一个分号(;)隔开的就是一条语句。当然只是基本上可以这么讲

int a=0;//1
printf("");//2
;//3

1,2,3都是语句(3是空语句,有时用到)。每条c语句在结束时大部分会加一个分号(不能乱加)。c语句有五类,本篇介绍控制语句。
也有其他情况是一条语句的
在这里插入图片描述
具体问题,具体分析。所以现在认为;隔开的就是一条语句即可

1.2控制语句

  1. c语言是一门结构化的程序设计语言。因为它支持三种结构:
    在这里插入图片描述
    顺序结构:从头走到尾。
    选择结构(分支语句):选择一项,而舍弃其他
    循环结构(循环语句):用于循环
  2. 控制语句
    用于控制程序流程,以实现程序的各种结构方式。共9种控制语句
    (1).分支语句:if、switch;
    (2).循环语句:while、for、do…while;
    (3).转向语句:break、continue、goto、return

2.分支语句(选择结构)

暑假你有两个选择
1是:打螺丝,挣学费
2是:连螺丝都不想打,摆烂
选1或2,而舍弃其他,这就是选择。
在这里插入图片描述

2.1if语句

2.1.1单分支

判断里面的表达式是否成立看的是真假。C语言非0表示真,0表示假。

if(表达式)
	语句;

表达式结果为真(非0),执行语句。否则,什么也不执行。
在这里插入图片描述

2.1.2双分支

if(表达式1)
	语句1;
else
	语句2;

表达式结果为真(非0),语句1执行。为假(0),语句1不执行,执行语句2。
在这里插入图片描述

2.1.3多分支

  1. 多分支
if(表达式1)
	语句1;
else if(表达式2)
	语句2;
else
	语句3;

表达式1成立,执行语句1;
表达式1不成立,表达式2成立,只执行语句2;
表达式1、2都不成立,执行语句3。
哪真走哪,并且剩余判断不进行。

  1. c语言中表达式的书写

在这里插入图片描述
age为10,但依然输出青年。因为18<=age<28书写错误
在这里插入图片描述
计算机不能像数学一样连着写,需要用到&&(逻辑且)
18<=age && age<28
2.多分支语句的实现
在这里插入图片描述
3.if语句的嵌套
在这里插入图片描述
这种写法和上面多分支2的写法效果一致。

2.1.4if语句的一些细节

  1. 多条语句的执行
    要执行多条语句,必须使用代码块({}),也叫花括号或者叫大括号。
if(表达式1)
{
	语句列表1;
}
else if(表达式2)
{
	语句列表2;
}

还有结尾可以写else,也可以是else if。区别在于,前者无条件表达式,前面不满足都会走else。后者有条件表达式,即使前面走完了,也需要判断表达式是否为真。

2.1.5悬空else

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

在这里插入图片描述
不打印。而且把代码拷贝进VS的时候,else自动缩进,与第二个if对齐了
在这里插入图片描述
题目上没有缩进,拷贝进VS的时候却自动缩进了。但其实它们的执行逻辑没有区别,把else的缩进回退,结果也是不变的。
else只与最近的未匹配的if进行配对(就近匹配)。而不论是否缩进,因为VS足够智能,判断出真正的执行逻辑。所以自动的缩进表示和第二个if匹配。因此,else是与第二个if,也就是if(a==1)匹配。
第一张图,a初始化为1,if(a==0)为假,压根就不进入下面的if(a==1)。而这个if(a==1)压根没有进入,所以什么都不会发生,更不要谈进入下面的else了。
因此代码的写法必须严谨,维持良好的代码规范,该缩进缩进,该回车回车。
如果非要else与第一个if匹配,可以加({})

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

输出结果就是“靓”。

2.1.6if书写形式的对比

  1. 代码1
if(condition){
	return x;
}
return y;

condition成立返回return x,条件不成立才能返回y。因为如果返回了x,y就没有机会了(return只要返回,程序结束,后面代码不执行)。而不是成不成立都返回y。这种书写很容易产生误解。

  1. 代码2
if(condition)
{
	return x;
}
esle
{
	return y;
}

这就比上面的代码更容易让人理解,我们就应该这么写,以免导致歧义。
3. return只要执行,程序立即结束(指的是return所在的函数结束)
在这里插入图片描述
再看下面这个形式的代码
在这里插入图片描述
它们的执行顺序完全一样,但是就是因为书写形式改变了,就造成了歧义。所以我们写代码的时候,该带大括号就带大括号,这是良好的代码风格。
4. 代码3
在这里插入图片描述
num明明是1,但if的条件判断为什么成立,打印了haha?
注意if(num=2)这里(=),是赋值,不是相等(相等是==)
num原为1,被i=2直接赋值,i为2真。进入if语句,打印haha。
哦,我们本意上是想判断i是否等于2,却因为只写了一个=,变成了赋值。编译器也没有直接暴露出你的错误,但是就这样写出了一个bug。
为了解决它,这样写if(2==num)把常量放在左边。因为2是常量,num是变量,变量放在右边,只能和常量比较相等。怎么能赋给常量呢?所以如果你写的是=,编译器会提醒你这里出错。
这样即使你少写了一个=,编译器也会报错,逼着你去加一个=。这样写想错都错不了。注意,未来要对一个常量和一个变量进行比较时,可以把常量写在左边,避免出错。

2.1.7练习

1.判断一个数是否为奇数
2.输出1-100之间的奇数
  1. 判断一个数是否为奇数。
    什么是奇数?不能被2整除的数。所以只需要取余看是否不为0,不为0就是奇数
    在这里插入图片描述
  2. 输出1~100之间的奇数
    结合前面的判断,只需要产生1~100之间的数,每次判断一下就可以了。
    在这里插入图片描述
    当然也可以去掉判断部分。我们要输出的是1~100之间的奇数,1是奇数,那么直接从1开始打印,每次加2,连判断都不用了。
    在这里插入图片描述

2.2switch语句

switch也是一种分支语句。常用于多分支,switch可以嵌套使用。(内部可出现if语句,continue不行)

switch(整型表达式)
{
	case (整型常量表达式1):
		语句1;
	caee (整型常量表达式2);
		语句2;
	......
}

必须是整型,float、char等报错。

  1. switch语句入口:case
    switch的整型表达式是几,它就从case几进去
    在这里插入图片描述
    输入7,从case 7进入。再尝试输入一下4
    在这里插入图片描述
    确实从case 4进去,但为什么还有星期五六七?这是因为,case是switch语句的入口,那你进去了,总得有个出口吧。没有出口就会导致程序从case 4开始一直玩下走,不会跳出。
  2. switch语句出口:break
    在case语句后面加上break就能跳出switch语句,下面的不再执行。
    在这里插入图片描述
    case 4进去,输出星期四,遇到break,直接结束switch语句,跳出switch。所以要精准输出星期几,要在每一个case后面都加上break
    在这里插入图片描述
    switch后面的表达式是几,就从入口case几进入。遇到break则跳出。
  3. switc和case的括号内的表达式
    switch(整型表达式):长短整型,int类型。可以是变量。
    case (整型常量表达式):整型常量的表达式。1+0也算,但是整个表达式的结果只能是整型常量。
  4. 什么时候用break
    并不是说每一个case后面都得加break,要看情况。比如说,星期一到星期五是工作日,周末是休息日,就可以这样。
    在这里插入图片描述
    所以并不是每一个case后面都要break,当多条case语句要执行同样的代码的时候,就可以把他们写在一起。所以具体情况具体分析。
  5. 编程好习惯
    csae7没有break,也会自动跳出switch,是可以不用break也能跳出的,但还是建议加。这是因为,当有一天你或者别的程序员,现在要处理的不只是1-7,而是多了一个8这样一种情况。而case 8需要处理的情况和case 7不一致。这个时候要是直接加上去,那么case8和case7就会有逻辑矛盾。输入是7,8的代码也会执行。所以末尾加上break是为了后人(也是自己)着想,这也是一种编程的好习惯。
  6. default默认
    前面说你输入的是1-7才能输出,那输入了9呢?什么也没有,因为匹配不了。default就是解决这种情况的
    在这里插入图片描述
    当输入的值都没有一个能与之匹配的case的话,它会走默认的default语句,里面我们可以放一点提示,提示你输入错误。当然里面也可以什么都不写。
    default语句是用来处理非法状况的,可有可无。它的位置在switch哪里都行,建议放在末尾。

3.循环语句

在这里插入图片描述
上大学时,你每天买彩票想要一夜暴富,没中就老实学习找个好工作。就这样买彩票,没中,学习,买彩票,没中,学习。
突然有一天,你中了999万,那还学个毛线啊。循环就终止,不学习了,去结婚了。或者当你天天学习,技术达到一定的地步了,成为了大牛,也终止循环,不学习了,找到了一份好工作结婚去了。
我们每天做事情,就叫做循环。当循环满足某种条件的时候,不再继续循环,循环终止。

  1. while循环
  2. for循环(常用)
  3. do…while循环(必须循环一次)

3.1while循环

3.1.1if语句与while语句

  1. if语句和while语句非常相似

在这里插入图片描述
if语句前面学过,(1)为真,所以打印haha。
在这里插入图片描述

()的判断部分为真,也是进入while语句里,打印haha。而打印haha结束后,程序会再次回到while的判断部分。因为一直为1,恒为真。所以死循环打印haha。

  1. 示例
    用while循环打印1-10的数字

在这里插入图片描述
while循环执行的逻辑是这样的
在这里插入图片描述
(1)先判断是否能进入。1<=10为真进入。
(2)打印完来到调整,n++就是调整,n+1后变成2,此时本次循环已经结束。
(3)再次来到判断部分。2<=10,还是符合。此后一直进行这样的循环。n加到10后。n<=10为真,打印10,n++变成11。11小于等于10不成立,所以循环终止

  1. 循环的三个部分
    n是用来控制循环的,叫循环变量
int main()
{
	int n = 0;//1.初始化
	while (n < 10)//2.判断
	{
		printf("%d ", n);
		n++;//3.调整
	}
	return 0;
}

循环变量n的首次创建叫初始化部分
“()”判断循环是否继续,是判断部分
对循环变量n进行修改叫调整

while图解
在这里插入图片描述
先判断expr是否为真,为真则执行循环体stmt。当执行完成循环体的最后一条语句时,会来到判断部分,再次判断expr是否为真。为真就继续循环,为假就跳出循环。就这样在123步之间循环。直到在某一次循环后,expr为0假,跳出循环。

  1. break终止循环(整个循环终止)
    在这里插入图片描述
    当循环体中遇到break后,整个循环会被终止
    n初始为1,不满足if(n==5),直到打印完4后,n++变为5。if(n==5)条件为真,执行break,使循环直接结束,来到这里。
    在这里插入图片描述
    要注意switch和循环中的break都只能跳出一层,并且是它所在的那层
  2. continue跳过本次循环后面的代码,直接来到判断部分
    在这里插入图片描述
    break的位置换成了continue。前面加到4的都一样。当n变为5时,满足if(n==5)continue执行,跳过后面的代码。循环变量的调整n++就没有机会执行,因此n不可能发生变化。而当来到while(n<=10)为真,又进入,又符合if(n==5),再次遇到continue,又跳过了n++。也就是说n根本没有被改变的机会。陷入死循环。
    在这里插入图片描述
    程序会一直重复这三步,又没有遇到打印,因此啥也不干。所光标一直在闪。只有当n++在continue前的时候,n才有改变的机会。
    在这里插入图片描述
    当n为5时,continue执行,跳过了printf,所以没有打印5。但因为n++在continue前,不受影响,所以n++变成6。此后,continue就没有机会执行了。
    总的来说,continue给我的感觉有点像是网页刷新。

3.1.2while实例

  1. getchar
    getchar接收一个字符,接收成功,返回该字符的ASCII码值。接收失败,则返回EOF,也就是-1。要么返回ASCII码值,要么返回-1。因此它的返回类型是int,而不是char。
    因为getchar没有参数,都是用返回值来接受字符的。所以getchar的使用方式一般都是用int ch = getchar();
    在这里插入图片描述
    功能和scanf很相似的,只是getchar专门用来处理字符,而且只能输入一个字符。
  2. putchar打印一个字符
    只能打印字符,不能用来打印整型。想要打印什么就把它或者变量放在括号里
    在这里插入图片描述
  3. EOF文件结束标志
    end of file 隐藏在文件的末尾,返回EOF则返回-1
    #define EOF -1
  4. getchar示例
    在这里插入图片描述
    作用是读一个字符就打印一个字符,直到getchar读取失败,返回EOF,即-1,使得while的判断部分wield假,跳出循环。当然在这里输入EOF是结束不了的。因为getchar是一个个读,实际上是分别读字母E,O,F。
    ctrl+z才能结束。它的作用是让scanf或getchar直接返回EOF
  5. 输入缓冲区
    给上一段模拟用户登录的代码
int main()
{
	char password[20] = {0};
	printf("请输入密码:");
	scanf("%s", password);
	printf("是否确认(Y/N)\n");
	int ret = getchar();
	if ('Y' == ret)
		printf("确认密码\n");
	else
		printf("放弃登录\n");
	return 0;
}

提示你输入密码,然后是确认还是放弃。但不论输入什么,输入完密码敲回车之后,getchar没有等待输入,直接放弃登录
要想解决这个问题,先要了解输入缓冲区
scanf和getchar等函数向计算机拿数据时,不是直接从键盘上拿的,还要经过输入缓冲区
在这里插入图片描述
原则上,scanf等要先看缓冲区有无数据,再判断是否等待。
所以在刚开始时,缓冲区什么都没有,scanf等待输入。输入了123456后,我们还敲了一个回车\n,也被缓冲区存了下来。
敲完回车那一刻,scanf把123456拿走,留下一个\n在输入缓冲区。而当代码执行到getchar的时候,它先看缓冲区,发现有个\n直接读走,就不等待输入了。所以根本没有机会输入。因此ret返回的是\n的ASCII码值。
在这里插入图片描述
所以,ret为\n,自然进入else里打印放弃。
那么只要把这个\n清理掉,让输入缓冲区什么也没有,不就可以让getchar等待了?
怎么清理?getchar不正好是读走一个字符吗?那再加一个getchar把\n读走不就好了。
在这里插入图片描述

但还有一种情况,假设输密码的时候不小心按了空格(空格也会使scanf停止读取,scanf只读空格前的),它依旧会和原来一样出错。(scanf在遇到空格和回车都会停止从输入缓冲区接收。但是空格能继续往输入缓冲区键入字符,而回车则会停止键入,执行余下代码。)
在这里插入图片描述
scanf拿走123456后,第一个getchar只能干掉一个字符,也就是那个空格,而到了ret的那个getchar只会读走空格,剩下的怎么清理?
因此你会发现这种方法的缺陷。但最后我肯定会敲一个回车来执行剩下的代码,而scanf读走空格前的,那只要把空格到\n的字符全部干掉就能解决了。最后为了结束键入,敲的一定是回车,那么只要给上一个循环,不读到\n,不终止不就行了。
而且,不需要做任何事,给上空语句,什么也不做
在这里插入图片描述

输入了密码123456后,又输入了空格和一些字符。但程序依旧停下来给我输入Y/N

  1. 只打印数字,而跳过其他字符
int main()
{
	char ch = '\0';
	while ((ch = getchar()) != EOF)
	{
		if (ch < '0' || ch > '9')
			continue;
		putchar(ch);
	}
	return 0;
}

判断部分只要getchar读取成功,就继续循环。因此可以输入任何字符。
if的条件是ch(用来接收getchar读取的字符的返回的ASCII码值)小于字符0或者大于字符9进入,只要读到的不是数字就一直进入。从而执行continue,让程序继续读取。
读到数字,那么不走if,直接打印。所以代码功能是遇到数字字符打印数字,不是数字继续接收字符。
在这里插入图片描述

我们在敲了1的时候,还敲了回车\n,但是\n是不是数字字符,所以会进入if,所以不会打印\n。
所以实际上是这样的

1
1光标闪烁

否则就是敲完1\n后是这样子的。

1
1
光标闪烁

字母光标换行是因为你本来就敲了回车键的效果,但不打印字母,光标就走到下面了

1
1光标闪烁

假设此时敲回车

1
1
光标闪烁

此时再敲一个a\n

1
1
a
光标闪烁

这个光标换行是因为你自己敲的那个回车导致的换行。
数字123,其实是ch先接收1,打印1。再接收2,打印2。最后接收3,打印3。
这里可能看不出来,但是我们可以验证一下
在这里插入图片描述

3.2for循环

  1. while循环的三个部分
int main()
{
	int i = 1;//1.初始化
	while (i <= 10)//2.判断
	{
		printf("%d ", i);
		i++;//3.调整
	}
	return 0;
}

每个部分改变都有可能会影响整个循环。未来,这个代码越来越庞杂的时候,这三个部分会越来越分散。而当你又要修改这某个部分的时候,你要兼顾其他两个。容易出错,因此while循环是不够理想的。for循环则整合了三个部分应运而生。

  1. for循环
for(表达式1;表达式2;表达式3)
	循环语句;

表达式1为初始化,用于初始化循环变量。
表达式2为条件判断,用于判断循环何时终止。
表达式3为调整,用于循环条件的调整。
用for循环打印1-10。
在这里插入图片描述

  1. for循环的执行流程
    在这里插入图片描述
    先初始化;在判断是否符合。符合,则执行循环体。否则,直接跳出;
    循环体执行结束,来到调整;调整完后,又回到2,判断是否符合。
    一直这样在234,234之间循环,直到某一次调整后,不符合2的条件。
    在这里插入图片描述
  2. for循环中的break和continue
    for的break和while基本一样。
    在这里插入图片描述
    都是永久终止循环。
    但continue有点差别。
    在这里插入图片描述
    i为5时,5<=10,进入循环。
    又符合i==5,执行continue
    但是continue是跳过本次循环后面的代码,第10行的printf被跳过了,来到调整i++。i变为6,此后continue都没有机会再次执行。
    和while不同的是,for循环的continue是不会把调整部分(除非把调整写在了循环体,并且在continue后)跳过的。而while循环则看调整的位置。
  3. for循环的三个部分不要轻易省略
    当i++放到循环体内时
    在这里插入图片描述
    因此建议不要在for语句的循环体内修改循环变量,防止for循环失去控制。
  4. 建议for语句的循环控制变量的取值采用“前开后闭区间”写法
    在这里插入图片描述
    当然也可以写成i<=9,但如果是前开后闭,那么这个10就有了某种意义。10次循环,10次打印。当然是这一般建议,也不是说所有的for循环都这么写,视情况而定。
  5. for循环的三个部分的省略
    for循环的三个部分是可以省略不写的,但建议写。否则容易出错。
    初始化和调整部分省略,啥也不做。
    判断部分省略,意味着判断恒为真。
    在这里插入图片描述
    啥也不写后,判断恒为真,死循环打印hehe。
    在这里插入图片描述
    int i=0;int j=0;已经有了声明,把它省略。但省略后,原来打印的是16个haha,省略初始化后只剩下4个
    在这里插入图片描述
    所以说不是特别熟练不要轻易省略任何一个部分。
  6. for循环变种
int x,y;
for (x = 0, y = 0; x < 2 && y < 5; ++x, y++)
{
	printf ("hehe\n");
}

初始化是可以有多个变量的,也可以是表达式,甚至不是变量,可能是一次函数调用。
判断和调整部分也是可以有多个的

  1. 例题
    问这个循环要循环多少次?
#include<stdio.h>
int main()
{
	int i = 0;
	int k = 0;
	for (i = 0, k = 0; k = 0; i++, k++)
		printf ("hehe\n");
	return 0;
}

答案是0。因为判断部分是k=0,把0赋值给k。判断即为0,0为假,这个循环压根就不进去

  1. for循环初始化直接声明
int i = 0;
for (i = 0; i < 10; i++)

这是一定不会错的写法。在for循环的前面先声明。

for (int i = 0; i < 10; i++)

这是c++的写法,当然c99标准也支持。在c语言里,这种写法需要编译器支持c99标准。因此建议使用第一种

3.3do…while循环

  1. 基本形式
do
{
	循环体;
} while (判断);
  1. do…while特点
    不管怎样,至少执行一次,适用于至少要执行一次循环的情况。
  2. do…while简单例子
    在这里插入图片描述
  3. do…while循环执行流程
    在这里插入图片描述
  4. do…while循环的break和continue
    break永久终止循环
    在这里插入图片描述
    continue跳过本次循环后面的代码,和while循环的类似。要注意continue和调整的位置。
    在这里插入图片描述
    当continue在调整前时,造成死循环。

3.4练习

3.4.1计算n的阶乘(忽略溢出)

  1. n的阶乘就是1*2*3…*n,只需要产生1~n的数字,互相乘起来就可以了
int main()
{
	int n = 0;
	int i = 0;
	int ret = 1;
	scanf("%d", &n);
	for (i = 1; i <= n; i++)
	{
		ret = ret * i;
	}
	printf("%d", ret);
	return 0;
}

记得包含头文件<stdio.h>
在这里插入图片描述

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

现在要的是10的阶乘,n是确定的值10,scanf和n都不需要了。

int main()
{
	int i = 0;
	int ret = 1;
	for (i = 1; i <= 10; i++)
	{
		ret = ret * i;
	}
	printf("%d", ret);
	return 0;
}

将每次循环得到阶乘都加上放到一个值里,10次后不就是答案了吗?用个sum来接收,sum又要声明

int main()
{
	int i = 0;
	int ret = 1;
	int sum = 0;
	for (i = 1; i <= 10; i++)
	{
		ret = ret * i;
	}
	printf("%d", ret);
	return 0;
}

把sum写到循环里,每次循环得到的阶乘ret加起来

int main()
{
	int i = 0;
	int ret = 1;
	int sum = 0;
	for (i = 1; i <= 10; i++)
	{
		ret = ret * i;
		sum = sum + ret;
	}
	printf("%d", ret);
	return 0;
}

而计算结果从阶乘变成了阶乘的和,所以printf访问的变量也应该从ret变成sum。

int main()
{
	int i = 0;
	int ret = 1;
	int sum = 0;
	for (i = 1; i <= 10; i++)
	{
		ret = ret * i;
		sum = sum + ret;
	}
	printf("%d", sum);
	return 0;
}

在这里插入图片描述
也可以for嵌套先求一个个阶乘再加,但是会有很多重复操作,运行久。而且没有初始化ret的值,容易出错。这里就不介绍了

3.4.3二分查找

在一个有序数组中查找具体的某个数字n。比如在1,2,3,4,5,6,7,8,9,10里找到7。

  1. 二分查找胎教原理
    你可能会想到这种算法
int main()
{
	int i = 0;
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	for (i = 0; i< 10; i++)
	{
		if (arr[i] == 7)
		{
			printf("找到了,下标是%d\n", i);
			break;
		}
	}
	if (i == 10)
	{
		printf("找不到\n");
	}
	return 0;
}

但当这组数很大时,比如有1亿个数,程序要运行多久?因此这种算法不理想,达不到我们想要的效果。
假设你有个朋友买了双鞋,他叫你猜这双鞋的价格。500,他说多了。你接着猜250,他说少了。那1-250的数就不用想了。然后你再猜375,他又说少了,250-375的数又干掉了。就这样猜,每次都干掉一半的数。这其实就是二分查找算法(也叫折半查找算法)
假设要你在1-10里找到7,那么就可以这样找。

在这里找7。将数组最左边的下标规定为left,最右边下标为right。中间元素下标为mid,也就是说我们是通过调整left和right,用mid锁定7的下标。如果arr[mid]==7,那就找到了。
在这里插入图片描述
第一次
在这里插入图片描述

第二次
在这里插入图片描述

第三次
在这里插入图片描述

第四次
在这里插入图片描述
最后,你找了4次就找到了7,相比如原来的7次,虽然看起来才少了3次,但这种算法的优化是指数级的。它最多只会找㏒₂n次,即使2的32次方个数字的数组,最多也才找32次,但是一个个找,比较费电脑。
所以说用二分查找来做这道题,将会使代码得到非常大的优化。

  1. 二分查找的实现
    上来就干,写一个框架
int main()
{
	return 0;
}

写完后,是要在1-10的数组里找7,那就要有这个数组和存放7的一个变量

int main()
{
	int arr[]={1,2,3,4,5,6,7,8,9,10};
	int k=7;
	return 0;
}

通过左右下标锁定中间元素下标来找7,那就得定义它们

int main()
{
	int arr[]={1,2,3,4,5,6,7,8,9,10};
	int k=7;
	int left=0;
	int right=sizeof(arr)/sizeof(arr[0])-1;    //整个数组的大小除以数组内一个元素的大小,得到数组元素个数。再减一得到数组最后元素下标
	int mid=(left+right)/2;
	return 0;
}

通过mid来找目标元素

int main()
{
	int arr[]={1,2,3,4,5,6,7,8,9,10};
	int k=7;
	int left=0;
	int right=sizeof(arr)/sizeof(arr[0])-1;
	
	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;
	}
	
	return 0;
}

而如果找不到,我们需要调整根据mid来调整left和right的位置,left一直右移,right一直左移。而在这个过程中mid也要根据左右下标的位置发生变化。即这是一个循环

int main()
{
	int arr[]={1,2,3,4,5,6,7,8,9,10};
	int k=7;
	int left=0;
	int right=sizeof(arr)/sizeof(arr[0])-1;
	while ()
	{
		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;
		}
	}
	return 0;
}

通过上面的原理,可知要是一直找不到,就会使left一直向右移,right向左移。最终,left和right相等的时候,还是没有找到,left右移或者right左移,就会导致left>right,而这组数也找完了。说明找完了的标准是left>right。而只要没找完,循环就一直进行。所以判断部分就应该为left<=right

int main()
{
	int arr[]={1,2,3,4,5,6,7,8,9,10};
	int k=7;
	int left=0;
	int right=sizeof(arr)/sizeof(arr[0])-1;
	int mid=(left+right)/2;
	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;
		}
	}
	return 0;
}

然而还没有完,如果找的是15,那么它根本找不到,我们得写点提示

int main()
{
	int arr[]={1,2,3,4,5,6,7,8,9,10};
	int k=7;
	int left=0;
	int right=sizeof(arr)/sizeof(arr[0])-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;
}

之所以要加上条件,是因为如果找到了退出循环,执行到printf,一定会打印“找不到”。就会导致,找到了,又找不到。因此要限定它的条件。

  1. 当然上述mid=(left+right)/2;存在缺陷
    一个整型的范围大概在-21亿~21亿。当left和right很大时,但没有超过int的范围,但它们加在一起就超过了int的范围,就会发生截断。
    在这里插入图片描述

所以我们可以这样写
在这里插入图片描述
当然还有其他多种写法在这里插入图片描述

3.4.4编写代码,演示多个字符从两端移动,向中间汇聚

  1. 原理
    在这里插入图片描述
    其实要达到这种效果,只要把第二行的第一个*改成h,最后一个*改成!。第二次再把第二个*改成e,倒数第二个*改成!。就这样一直改,改成到最后*全部没有了。就做到了这种效果。改的位置是对应的,那么我给上左右下标。
    在这里插入图片描述
    此时只要将arr1[left]赋值给arr2[left],arr1[right]赋值给arr2[right],不就做到了交换吗?然后left右移,right左移,再赋值,又改掉一对。如此循环,最终得到演示的效果。
    在这里插入图片描述
  2. 实现
    首先定义数组
#include <stdio.h>
int main()
{
	char arr1[] = "hello world!!!!!!!!"; 
	char arr2[] = "*******************";
	return 0;
}

然后通过下标移动替换它们的值,定义左右下标

#include <stdio.h>
#include <string.h>    //strlen的头文件
int main()
{
	char arr1[] = "hello world!!!!!!!!"; 
	char arr2[] = "*******************";
	int left = 0;
	int right = strlen(arr1) - 1;    //strlen就算arr1的长度,长度-1就是最后元素下标
	return 0;
}

然后赋值修改

#include <stdio.h>
#include <string.h>    //strlen的头文件
int main()
{
	char arr1[] = "hello world!!!!!!!!"; 
	char arr2[] = "*******************";
	int left = 0;
	int right = strlen(arr1) - 1;    //strlen就算arr1的长度,长度-1就是最后元素下标
	arr2[left] = arr1[left];
	arr2[right] = arr1[right];
	return 0;
}

赋值完之后,让left右移,right左移

#include <stdio.h>
#include <string.h>    //strlen的头文件
int main()
{
	char arr1[] = "hello world!!!!!!!!"; 
	char arr2[] = "*******************";
	int left = 0;
	int right = strlen(arr1) - 1;    //strlen就算arr1的长度,长度-1就是最后元素下标
	arr2[left] = arr1[left];
	arr2[right] = arr1[right];
	left++;
	right--;
	return 0;
}

但这个赋值,和左右下标的移动要进行很多次,是一个循环的过程。给上一个while

#include <stdio.h>
#include <string.h>    //strlen的头文件
int main()
{
	char arr1[] = "hello world!!!!!!!!"; 
	char arr2[] = "*******************";
	int left = 0;
	int right = strlen(arr1) - 1;    //strlen就算arr1的长度,长度-1就是最后元素下标
	while()
	{
		arr2[left] = arr1[left];
		arr2[right] = arr1[right];
		left++;
		right--;
	}
	return 0;
}

那while的判断如何进行?left左移,right右移,最终在替换最后一个或两个时,就已经完成了任务,也就是说left>right的时候,任务就完成了。所以判断部分应为left<=right

#include <stdio.h>
#include <string.h>    //strlen的头文件
int main()
{
	char arr1[] = "hello world!!!!!!!!"; 
	char arr2[] = "*******************";
	int left = 0;
	int right = strlen(arr1) - 1;    //strlen就算arr1的长度,长度-1就是最后元素下标
	while(left<=right)
	{
		arr2[left] = arr1[left];
		arr2[right] = arr1[right];
		left++;
		right--;
	}
	return 0;
}

所有逻辑都完成了,但还缺少输出。而且,我希望每次替换都能看到,所以printf应该在循环内部。

#include <stdio.h>
#include <string.h>    //strlen的头文件
int main()
{
	char arr1[] = "hello world!!!!!!!!"; 
	char arr2[] = "*******************";
	int left = 0;
	int right = strlen(arr1) - 1;    //strlen就算arr1的长度,长度-1就是最后元素下标
	while(left<=right)
	{
		arr2[left] = arr1[left];
		arr2[right] = arr1[right];
		printf("%s\n", arr2);
		left++;
		right--;
	}
	return 0;
}

最终就实现了这样的效果
在这里插入图片描述
还可以再搞点花样
让每次输出都等待一下

#include <stdio.h>
#include <string.h>    //strlen的头文件
#include <Windows.h>   //Sleep的头文件
int main()
{
	char arr1[] = "hello world!!!!!!!!"; 
	char arr2[] = "*******************";
	int left = 0;
	int right = strlen(arr1) - 1;    //strlen就算arr1的长度,长度-1就是最后元素下标
	while(left<=right)
	{
		arr2[left] = arr1[left];
		arr2[right] = arr1[right];
		printf("%s\n", arr2);
		Sleep(1000);  //Sleep休眠,睡眠 单位毫秒
		left++;
		right--;
	}
	return 0;
}

还可以每次打印完,就清屏一下

#include <stdio.h>
#include <string.h>    //strlen的头文件
#include <Windows.h>   //Sleep的头文件
#include <stdlib.h>  //system的头文件
int main()
{
	char arr1[] = "hello world!!!!!!!!"; 
	char arr2[] = "*******************";
	int left = 0;
	int right = strlen(arr1) - 1;    //strlen就算arr1的长度,长度-1就是最后元素下标
	while(left<=right)
	{
		arr2[left] = arr1[left];
		arr2[right] = arr1[right];
		printf("%s\n", arr2);
		Sleep(1000);  //Sleep休眠,睡眠 单位毫秒
		system("cls");  //system执行命令,相当于电脑的cmd命名窗口。
		//cls清屏
		left++;
		right--;
	}
	return 0;
}

当然,执行到最后一次的时候,cls把最后一次也干掉了,可以再加上去

#include <stdio.h>
#include <string.h>    //strlen的头文件
#include <Windows.h>   //Sleep的头文件
int main()
{
	char arr1[] = "hello world!!!!!!!!";
	char arr2[] = "*******************";
	int left = 0;
	int right = strlen(arr1) - 1;    //strlen就算arr1的长度,长度-1就是最后元素下标
	while (left <= right)
	{
		arr2[left] = arr1[left];
		arr2[right] = arr1[right];
		printf("%s\n", arr2);
		Sleep(1000);  //Sleep休眠,睡眠 单位毫秒
		system("cls");  //system执行命名,相当于电脑的cmd命名窗口
		//cls清屏
		left++;
		right--;
	}
	printf("%s", arr2);
	return 0;
}

3.4.5编写代码实现,模拟用户登录情景。

只允许输入三次密码,如果密码正确则提示登录成功,如果三次均输入错误,则退出程序

  1. 原理
    三次输入密码,也就是循环咯。上来就干,给上循环。
int main()
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		
	}
	return 0;
}

循环要做的事就是输入密码,那么给上scanf。而scanf输入的密码有需要空间来放,和头文件

#include <stdio.h>
int main()
{
	int i = 0;
	char password[20] = {0};  //密码是一串字符
	for (i = 0; i < 3; i++)
	{
		scanf("%s", password);  //数组的本质是地址,不需要&
	}
	return 0;
}

假设密码是123456。密码正确则登录成功,错误则再次输入,理应是两种情况,使用分支语句

#include <stdio.h>
int main()
{
	int i = 0;
	char password[20] = {0};  //密码是一串字符
	for (i = 0; i < 3; i++)
	{
		scanf("%s", password);  //数组的本质是地址,不需要&
		if (password == "123456")
		{
			printf("登录成功\n");
			break;  //一旦密码正确,则停止输入,退出循环
		}
		else
		{
			printf("输入错误\n");
		}
	}
	return 0;
}

三次输入错误后,退出循环,要提示一下。而且要判断一下,否则登录成功也会打印错误过多,请明天再试

#include <stdio.h>
int main()
{
	int i = 0;
	char password[20] = {0};  //密码是一串字符
	for (i = 0; i < 3; i++)
	{
		scanf("%s", password);  //数组的本质是地址,不需要&
		if (password == "123456")
		{
			printf("登录成功\n");
			break;  //一旦密码正确,则停止输入,退出循环
		}
		else
		{
			printf("输入错误\n");
		}
	}
	if (i == 3)
	{
		printf("错误过多,请明天再试\n");
	}
	return 0;
}

别以为到这里就行了,ctrl+f5压根运行不起来。是因为字符串比较相等不能用==,而是strcmp

#include <stdio.h>
#include <string.h>    //strcmp的头文件
int main()
{
	int i = 0;
	char password[20] = {0};  //密码是一串字符
	for (i = 0; i < 3; i++)
	{
		scanf("%s", password);  //数组的本质是地址,不需要&
		//strcmp比较两个字符串
		//返回0,两个字符串相等
		//返回>0的数字,表示第一个字符串>第二个字符串
		//返回<0的数字,表示第一个字符串<第二个字符串
		if (strcmp(password, "123456") == 0)
		{
			printf("登录成功\n");
			break;  //一旦密码正确,则停止输入,退出循环
		}
		else
		{
			printf("输入错误\n");
		}
	}
	if (i == 3)
	{
		printf("错误过多,请明天再试\n");
	}
	return 0;
}

两个字符串大小的比较,比的是对应位置的ASCII码值
在这里插入图片描述

3.5猜数字

实现一个猜数字游戏

  1. 电脑随机生产一个数
  2. 猜数字
    a>猜大了,提醒猜大了,继续猜
    b>猜小了,提醒猜小了,继续猜
    c>猜对了,结束这把游戏
  3. 玩一把,不够,继续玩

3.5.1分析

要做的事有四件
游戏得有个菜单,打印它
产生随机数;
猜随机数,猜大了或猜小了有提示,继续猜。猜对了结束这把游戏
玩完一把,还不够,选择是否继续玩

  1. 打印菜单
    选择用函数来封装,使得看起来更简洁
#include <stdio.h>

void menu()  //只需要打印,不需要返回值,无参
{
	printf("****************************\n");
	printf("******1.play  0.exit********\n");
	printf("****************************\n");
}
int main()
{
	menu();
	return 0;
}
  1. 选择玩游戏还是退出程序,两种情况,分支语句
#include <stdio.h>

void menu()  //只需要打印,不需要返回值,无参
{
	printf("****************************\n");
	printf("******1.play  0.exit********\n");
	printf("****************************\n");
}
int main()
{
	int input = 0;
	menu();
	printf("请选择\n");
	scanf("%d", &input);
	switch(input)
	{
		case 1:
			gaem();  //封装的游戏过程,还没定义函数
			break;
		case 0:     //易写成2
			printf("退出程序\n");
			break;
		default:
			printf("输入错误\n");
			break;
	}
	return 0;
}
  1. 每次选择,玩游戏还是退出,有多次,所以从菜单到退出是个循环体。至少玩一把,do…while更合适
#include <stdio.h>

void menu()  //只需要打印,不需要返回值,无参
{
	printf("****************************\n");
	printf("******1.play  0.exit********\n");
	printf("****************************\n");
}
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			gaem();  //封装的游戏过程,还没定义函数
			break;
		case 0:     //易写成2
			printf("退出程序\n");
			break;
		default:
			printf("输入错误\n");
			break;
		}
	} while ();
	return 0;
}
  1. 判断部分,输入input为1,玩游戏循环继续。输入0,退出游戏,退出循环,那么input作为判断部分正好合适。
#include <stdio.h>

void menu()  //只需要打印,不需要返回值,无参
{
	printf("****************************\n");
	printf("******1.play  0.exit********\n");
	printf("****************************\n");
}
int main()
{
	int input = 0;
	menu();
	do
	{
		menu();
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			gaem();  //封装的游戏过程,还没定义函数
			break;
		case 0:     //易写成2
			printf("退出程序\n");
			break;
		default:
			printf("输入错误\n");
			break;
		}
	} while (input);
	return 0;
}
  1. gaem函数的封装
    使用rand函数,生成一个0-32767随机数(返回值)
#include <stdio.h>
#include <stdlib.h>

void menu()  //只需要打印,不需要返回值,无参
{
	printf("****************************\n");
	printf("******1.play  0.exit********\n");
	printf("****************************\n");
}
void gaem()  //进行游戏,不需要返回值
{
	int ret = rand();    //接收这个随机数
	printf("%d", ret);
}
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			gaem();  //封装的游戏过程,还没定义函数
			break;
		case 0:     //易写成2
			printf("退出程序\n");
			break;
		default:
			printf("输入错误\n");
			break;
		}
	} while (input);
	return 0;
}

在这里插入图片描述

每次运行内部确实生成随机数了,但当再重新编译一次代码,随机数也是这个。两次运行的代码内部随机数是一样的。这是因为使用rand生成随机数时,要先使用srand设置随机数的生成起点(语法规定的,没有为什么)。
在这里插入图片描述
srand不需要返回,参数是unsigned int,那我给个1,也算是调用了

#include <stdio.h>
#include <stdlib.h>

void menu()  //只需要打印,不需要返回值,无参
{
	printf("****************************\n");
	printf("******1.play  0.exit********\n");
	printf("****************************\n");
}
void gaem()  //进行游戏,不需要返回值
{
	srand(1);
	int ret = rand();    //接收这个随机数
	printf("%d", ret);
}
int main()
{
	int input = 0;
	do
	{
		menu();  //使主函数更简洁
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			gaem();  //封装的游戏过程,还没定义函数
			break;
		case 0:     //易写成2
			printf("退出程序\n");
			break;
		default:
			printf("输入错误\n");
			break;
		}
	} while (input);
	return 0;
}

但还是发生了问题
在这里插入图片描述
随机数不随机了,srand放个2试试
在这里插入图片描述
虽然随机数发生了变化,但依旧一直打印一个随机数。但也说明了一个问题,srand里的值如果固定,那么rand生成的随机数就固定。也就是说,我只要放一个变化的值到srand里就可以了。
灵光一闪那就放随机数吧。不过搁着念魔咒呢,rand需要生成随机,要先使用srand,srand又要一个随机数,就形成先有鸡还是现有蛋的问题。
解决的办法就是放把时间戳放进srand
时间戳是1970-1-1开始到现在的秒数,每秒都在变化
在这里插入图片描述
time头文件时time.h。作用就是返回一个时间戳,但time是time_t类型。这是什么类型?选中time_t,右击鼠标转到定义
在这里插入图片描述
而time_t又是被重定义过来的
在这里插入图片描述
再次对__time_t转到定义。
在这里插入图片描述
最后发现转不过去了,不过当鼠标放到_int64的时候显示long long类型。_int64是指定一个64位的整型变量,即8个字节,属于long long类型,不符合srand的类型,强制转换。
还有要注意的是,我们只需要用time函数来返回时间戳就可以了,不需要time的参数。所以参数给上NULL,空的意思

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void menu()  //只需要打印,不需要返回值,无参
{
	printf("****************************\n");
	printf("******1.play  0.exit********\n");
	printf("****************************\n");
}
void gaem()  //进行游戏,不需要返回值
{
	srand((unsigned int)time(NULL));
	int ret = rand();    //接收这个随机数
	printf("%d\n", ret);
}
int main()
{
	int input = 0;
	do
	{
		menu();  //使主函数更简洁
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			gaem();  //封装的游戏过程,还没定义函数
			break;
		case 0:     //易写成2
			printf("退出程序\n");
			break;
		default:
			printf("输入错误\n");
			break;
		}
	} while (input);
	return 0;
}

在这里插入图片描述
但是又出现了一个问题,那就是时间戳1秒变一次,rand生成的随机数很近,尤其是点的快的时候,压根就不变。
这是因为srand不能频繁调用,整个工程只需要调用一次就可以,不是每生成一次随机数就调用一次。
否则就会像上面一样,一直在生成随机起点,每次rand产生的随机数都会以srand新生成的随机起点生成随机数,时间太近就导致了srand(固定数),所以rand生成的随机数一样。
而只要我把srand放到主函数,它就只调用了一次

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void menu()  //只需要打印,不需要返回值,无参
{
	printf("****************************\n");
	printf("******1.play  0.exit********\n");
	printf("****************************\n");
}
void gaem()  //进行游戏,不需要返回值
{
	int ret = rand();    //接收这个随机数
	printf("%d\n", ret);
}
int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));  //调用一次即可
	do
	{
		menu();  //使主函数更简洁
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			gaem();  //封装的游戏过程,还没定义函数
			break;
		case 0:     //易写成2
			printf("退出程序\n");
			break;
		default:
			printf("输入错误\n");
			break;
		}
	} while (input);
	return 0;
}

在这里插入图片描述
随机数不相连,我已经点的飞快了。说明随机数这块,彻底搞定了。
接下来就进行猜大猜小的提示,属于多种情况。并且把printf删掉,不能看随机数了

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void menu()  //只需要打印,不需要返回值,无参
{
	printf("****************************\n");
	printf("******1.play  0.exit********\n");
	printf("****************************\n");
}
void gaem()  //进行游戏,不需要返回值
{
	int ret = rand();    //接收这个随机数
	int num = 0;
	scanf("%d", &num);
	if(num > ret)
	{
		printf("猜大了\n");
	}
	else if(num < ret)
	{
		printf("猜小了\n");
	}
	else
	{
		printf("猜对了\n");
		break;    //猜对了,退出猜数字
	}
}
int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));  //调用一次即可
	do
	{
		menu();  //使主函数更简洁
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			gaem();  //封装的游戏过程,还没定义函数
			break;
		case 0:     //易写成2
			printf("退出程序\n");
			break;
		default:
			printf("输入错误\n");
			break;
		}
	} while (input);
	return 0;
}

而且我们说,猜不对就要一直猜,因此它是一个循环

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void menu()  //只需要打印,不需要返回值,无参
{
	printf("****************************\n");
	printf("******1.play  0.exit********\n");
	printf("****************************\n");
}
void gaem()  //进行游戏,不需要返回值
{
	int ret = rand();    //接收这个随机数
	int num = 0;
	while(1)   //只有才对猜给出来
	{
	scanf("%d", &num);
	if(num > ret)
	{
		printf("猜大了\n");
	}
	else if(num < ret)
	{
		printf("猜小了\n");
	}
	else
	{
		printf("猜对了\n");
		break;    //猜对了,退出猜数字
	}
	}
}
int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));  //调用一次即可
	do
	{
		menu();  //使主函数更简洁
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			gaem();  //封装的游戏过程,还没定义函数
			break;
		case 0:     //易写成2
			printf("退出程序\n");
			break;
		default:
			printf("输入错误\n");
			break;
		}
	} while (input);
	return 0;
}

到这其实也就完成了,但0-32767的数字,未免也太难猜了吧。对rand()%100。任何数%100,结果都只能为0-99。也就是说ret变成了0-99,好看点再加个1,变成1-100

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void menu()  //只需要打印,不需要返回值,无参
{
	printf("****************************\n");
	printf("******1.play  0.exit********\n");
	printf("****************************\n");
}
void gaem()  //进行游戏,不需要返回值
{
	int ret = rand() % 100 + 1;    //接收这个随机数
	int num = 0;
	while (1)   //只有才对猜给出来
	{
		printf("猜数字\n");
		scanf("%d", &num);
		if (num > ret)
		{
			printf("猜大了\n");
		}
		else if (num < ret)
		{
			printf("猜小了\n");
		}
		else
		{
			printf("猜对了\n");
			break;    //猜对了,退出猜数字
		}
	}
}
int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));  //调用一次即可
	do
	{
		menu();  //使主函数更简洁
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			gaem();  //封装的游戏过程,还没定义函数
			break;
		case 0:     //易写成2
			printf("退出程序\n");
			break;
		default:
			printf("输入错误\n");
			break;
		}
	} while (input);
	return 0;
}

3.6goto语句

要是看过火影,你就看成飞雷神就可以了
C语言中提供了可以随意滥用的goto语句和标记跳转的标号
理论上goto语句没有存在的必要,实践中没有goto语句也能写出代码
但是某些场合下goto语句还是用得着的,最常见的就是终止程序在某些深度嵌套的结果的处理过程,即跳出深度嵌套
例如:一次跳出两层或多层循环
多层循环要完全跳出break是做不到的,break只能跳出自己所在的那一层循环
goto适合的场景

for(...)
{
	for(...)
	{
		for(...)
		{
			if(condition)   //当条件发生
			{
				goto flag;  //直接去到flag的位置
			}
		}
	}
}
flag:       //跳到这里,flag只是个标记,可以自己起名
	if(condition)
	     //处理特殊情况
  1. goto的示例
#include <stdio.h>
int main()
{
flag:
	printf("hehe\n");
	goto flag;  //跳转到flag
	return 0;
}

运行代码会死循环打印hehe
在这里插入图片描述
第一次打印hehe,来到goto语句,跳转到flag,第二次打印hehe。又来到goto语句,又跳到flag,又打印hehe。123的步骤反复执行,就死循环打印hehe了。

  1. gotod的标记可以在前可以在后,但要顶格
    在前时,死循环打印hehe
#include <stdio.h>
int main()
{
flag://顶格
	printf("hehe\n");
	goto flag;  //跳转到flag
	return 0;
}

在后时,跳过printf,不打印hehe

#include <stdio.h>
int main()
{
	goto flag;  //跳转到flag
	printf("hehe\n");
flag://顶格
	return 0;
}
  1. goto语句只能在一个函数内部跳转,不能跨函数跳转
    在这里插入图片描述
  2. goto语句适用场景
    用goto语句写一个关机程序
    要求程序运行起来2分钟之后电脑关机,如果输入:我是大笨猪,就取消关机
    要想实现这个功能,先得认识关机命令
    在电脑上搜索cmd,它是一个命令提示符窗口,通过输入命令,执行系统操作
    在这里插入图片描述
    shutdown -s -t 120
    shutdown -s表示设置关机,-t 120表示120秒之后关机。注意如果你这里真的什么都不做,电脑真的会关机
    用shutdown -a取消关机
    在这里插入图片描述
    上来就干,直接让他关机
#include <stdio.h>
int main()
{
	system("shutdown -s -t 120");   //system执行命令,之前输入cls用来清屏过。shutdowm -s设置关机   -t 120   2分钟后关机
	return 0;
}

而要求输入我是大笨猪,取消关机。那么要用scanf输入,scanf还要一个变量来存

#include <stdio.h>
int main()
{
	system("shutdown -s -t 120");   //system执行命令,之前输入cls用来清屏过。shutdowm -s设置关机   -t 120   2分钟后关机
	char input[20] = { 0 };
	prntf("输入:我是大笨猪,取消关机\n");
	scanf("%s", input);
	return 0;
}

假设他很乖,赶紧输入了我是大笨猪,那么就帮他取消关机吧

#include <stdio.h>
#include <string.h>  //strcmp的头文件
int main()
{
	system("shutdown -s -t 120");   //system执行命令,之前输入cls用来清屏过。shutdowm -s设置关机   -t 120   2分钟后关机
	char input[20] = { 0 };
	printf("输入:我是大笨猪,取消关机\n");
	scanf("%s", input);
	if (strcmp(input, "我是大笨猪") == 0)
	{
		system("shutdown -a");  //取消关机
	}
	return 0;
}

再假设他又不乖了或者紧张一不小心输错了,给机会他,让他在这2分钟能一直输入。这里就可以用到goto语句了。

#include <stdio.h>
#include <string.h>    //strcmp的头文件
int main()
{
	system("shutdown -s -t 120");   //system执行命令,之前输入cls用来清屏过。shutdowm -s设置关机   -t 120   2分钟后关机
	char input[20] = { 0 };
flag:
	printf("输入:我是大笨猪,取消关机\n");
	scanf("%s", input);
	if (strcmp(input, "我是大笨猪") == 0)
	{
		system("shutdown -a");  //取消关机
	}
	else         //不给上else会导致死循环
	{
		goto flag;
	}
	return 0;
}

每次输入错误,都给机会重新输入在这里插入图片描述
这里已经开始等待关机,那么只要输入我是大笨猪就可以取消关机了
在这里插入图片描述

只有输入我是大笨猪,才会取消关机。

  1. goto语句不是必要的,可以替换
#include <stdio.h>
#include <string.h>    //strcmp的头文件
int main()
{
	system("shutdown -s -t 120");   //system执行命令,之前输入cls用来清屏过。shutdowm -s设置关机   -t 120   2分钟后关机
	char input[20] = { 0 };
flag:
	printf("输入:我是大笨猪,取消关机\n");
	scanf("%s", input);
	if (strcmp(input, "我是大笨猪") == 0)
	{
		system("shutdown -a");  //取消关机
	}
	else         //不给上else会导致死循环
	{
		goto flag;
	}
	return 0;
}

要求输入我是大笨猪,输入错误,再次输入,你会发现这是个循环的过程。

#include <stdio.h>
#include <string.h>    //strcmp的头文件
int main()
{
	system("shutdown -s -t 120");   //system执行命令,之前输入cls用来清屏过。shutdowm -s设置关机   -t 120   2分钟后关机
	char input[20] = { 0 };
	while(1)
	{  //其实这里应该还要说明电脑关机,前面忘写了,懒得改了
		printf("注意,你的电脑将在2分钟后关机,输入:我是大笨猪,取消关机\n");
		scanf("%s", input);
		if (strcmp(input, "我是大笨猪") == 0)
		{
			system("shutdown -a");  //取消关机
			break;  //取消关机成功,退出程序
		}
	}
	return 0;
}

也是能得到一样的效果,因此goto语句在当前时不必要的。能不用就不用。

3.7巩固

3.7.1打印1-100之间所有3的倍数

要打印的是3的倍数,那么首先得产生1-100。循环走起来

#include <stdio.h>
int main()
{
	int i = 0;
	for (i = 1; i < 100; i++)
	{
		
	}
	return 0;

}

只打印3的倍数,要判断一下是否为3的倍数

#include <stdio.h>
int main()
{
	int i = 0;
	for (i = 1; i < 100; i++)
	{
		if (i % 3 == 0)
		{
			printf("%d ", i);   //是3的倍数就进入if,打印
		}
	}
	return 0;
}

当然也可以这样去做。首次打印3,每次加3,依旧还是3的倍数,就省略了判断的部分

#include <stdio.h>
int main()
{
	int i = 0;
	for (i = 3; i < 100; i += 3)
	{
		printf("%d ", i);
	}
	return 0;
}

3.7.2将3个整数从大到小输出

能够输入3个数字

#include <stdio.h>
int main()
{
	int a = 0;
	int b = 0;
	int c = 0;
	scanf("%d %d %d", &a, &b, &c);
}

输出abc,然后判断它们的大小,做到a>b>c即可

#include <stdio.h>
int main()
{
	int a = 0;
	int b = 0;
	int c = 0;
	scanf("%d %d %d", &a, &b, &c);
	printf("%d %d %d\n", a, b, c);
}

那也就是所如果a小于另外两个,那我就把它们的值交换。最后再比较b和c,保证b>c。
有人可能会想到这样交换

#include <stdio.h>
int main()
{
	int a = 0;
	int b = 0;
	int c = 0;
	scanf("%d %d %d", &a, &b, &c);
	if (a < b)   //a>b啥也不用干
	{
		a = b;
		b = a;
	}
	printf("%d %d %d\n", a, b, c);
}

然而这是错的
在这里插入图片描述
C语言两个值的交换类似于生活中这样的场景。把酱油放到老抽的瓶子了,把老抽放到酱油的瓶子里
在这里插入图片描述
显然只有两个容器的情况下,你无法做到交换。这时候找一个空瓶子
先将酱油放到空瓶,使酱油瓶变空
在这里插入图片描述
再将老抽倒入酱油瓶
在这里插入图片描述
最后将空瓶的酱油倒入老抽瓶
在这里插入图片描述
在这里插入图片描述
最终实现它们之间的交换。
C语言的两个变两交换亦是如此。得有一个中间变量,用来临时存放值的。

#include <stdio.h>
int main()
{
	int a = 0;
	int b = 0;
	int c = 0;
	scanf("%d %d %d", &a, &b, &c);
	if (a < b)
	{
		int tmp = a;
		a = b;
		b = tmp;
	}
	if (a < c)       //可不敢输else if,只能走一个,这里三个都要判断
	{
		int tmp = a;
		a = c;
		c = tmp;
	}
	if (b < c)
	{
		int tmp = b;
		b = c;
		c = tmp;
	}
	printf("%d %d %d\n", a, b, c);
}

3.7.3打印100-200之间的素数

首先你要明白什么是素数
素数:只能被1和它本身整除的数字
在这里插入图片描述
也就是说要判断n是否为素数,那就拿2到n-1的数去试除它。如果这些数都不能整除n,n就是素数。但如果只要有一个数能整除n,n都不是素数。
思路清楚之后就开始考虑怎么写代码了

  1. 首先要判断100-200的数,那先得有这些数
int main()
{
	int n = 0;
	for (n = 100; n <= 200; n++)
	{
		
	}
	return 0;
}

有了这些数之后,要拿2到n-1的数字去试除n,拿就要产生这些数字,依然是用循环

int main()
{
	int n = 0;
	for (n = 100; n <= 200; n++)
	{
		int i = 0;
		for (i = 2; i <= n - 1; i++)
		{
			
		}
	}
	return 0;
}

然后每次产生一个2到n-1的数,我都判断一下能否整除。然后用flag做个标记,如果能整除,flag就改变,反之flag不变,那么n就是素数

#include <stdio.h>
int main()
{
	int n = 0;
	for (n = 100; n <= 200; n++)
	{
		int i = 0;
		int flag = 1;
		for (i = 2; i <= n - 1; i++)
		{
			if (n % i == 0)     //能整除就不是素数,改变flag     
			{
				flag = 0;
				break;        //只要有一个能整除,就不是素数,后面的都不用判断了
			}
		}
		if (flag)   //是素数没有改变,为真进入  不是素数,flag改为0假,不进入
		{
			printf("%d ", n);
		}
	}
	return 0;
}

当然,还可以看看有多少个素数

#include <stdio.h>
int main()
{
	int n = 0;
	int count = 0;        //用来计数
	for (n = 100; n <= 200; n++)
	{
		int i = 0;
		int flag = 1;
		for (i = 2; i <= n - 1; i++)
		{
			if (n % i == 0)     //能整除就不是素数,改变flag     
			{
				flag = 0;
				break;        //只要有一个能整除,就不是素数,后面的都不用判断了
			}
		}
		if (flag)   //是素数没有改变,为真进入  不是素数,flag改为0假,不进入
		{
			printf("%d ", n);
			count++;  //只要是素数就加1
		}
	}
	printf("\ncount=%d\n", count);
	return 0;
}

这只是一种算法,还有其他更简洁的算法

  1. 优化1
    偶数能被2整除,那么偶数不可能是素数。因此,从101开始判断,每次加2,就把一半的代码干掉了
#include <stdio.h>
int main()
{
	int n = 0;
	int count = 0;        //用来计数
	for (n = 101; n <= 200; n += 2)
	{
		int i = 0;
		int flag = 1;
		for (i = 2; i <= n - 1; i++)
		{
			if (n % i == 0)     //能整除就不是素数,改变flag     
			{
				flag = 0;
				break;        //只要有一个能整除,就不是素数,后面的都不用判断了
			}
		}
		if (flag)   //是素数没有改变,为真进入  不是素数,flag改为0假,不进入
		{
			printf("%d ", n);
			count++;  //只要是素数就加1
		}
	}
	printf("\ncount=%d\n", count);
	return 0;
}
  1. 优化2
    在这里插入图片描述

所以压根不用试除到n-1,试除到开平方n就可以了

#include <stdio.h>
#include <math.h>  //库函数sqrt的头文件
int main()
{
	int n = 0;
	int count = 0;        //用来计数
	for (n = 101; n <= 200; n += 2)
	{
		int i = 0;
		int flag = 1;
		//sqrt是库函数,开平方的意思
		//sqrt(i)就是对i开平方
		for (i = 2; i <= sqrt(n); i++)
		{
			if (n % i == 0)     //能整除就不是素数,改变flag     
			{
				flag = 0;
				break;        //只要有一个能整除,就不是素数,后面的都不用判断了
			}
		}
		if (flag)   //是素数没有改变,为真进入  不是素数,flag改为0假,不进入
		{
			printf("%d ", n);
			count++;  //只要是素数就加1
		}
	}
	printf("\ncount=%d\n", count);
	return 0;
}

在这里插入图片描述
本质上这些方法都是试除法,判断素数并不止试除法

3.7.4打印1000-2000之间的闰年

闰年的判断规则
1.能被4整除,并且不能被100整除
2.能被400整除的闰年


  1. 首先得有1000-2000之间的数,才能谈是否为闰年
int main()
{
	int y = 0;
	for (y = 1000; y <= 2000; y++)
	{
		//判断闰年
	}
	return 0;
}

产生了这些数字,就要判断它是否为闰年。是就打印

#include <stdio.h>
int main()
{
	int y = 0;
	for (y = 1000; y <= 2000; y++)
	{
		if (y % 4 == 0 && y % 100 != 0)
		{
			printf("%d ", y);
		}
		if (y % 400 == 0)
		{
			printf("%d ", y);
		}
	}
	return 0;
}

还可以计算一下个数

#include <stdio.h>
int main()
{
	int y = 0;
	int count = 0;
	for (y = 1000; y <= 2000; y++)
	{
		if (y % 4 == 0 && y % 100 != 0)
		{
			printf("%d ", y);
			count++;
		}
		if (y % 400 == 0)//这里换成else也是可以的
		{
			printf("%d ", y);
			count++;
		}
	}
	printf("\ncount=%d\n", count);
	return 0;
}

在这里插入图片描述
如果是240个,可能代码是这样。会漏掉1200,1600,2000。

#include <stdio.h>
int main()
{
	int y = 0;
	int count = 0;
	for (y = 1000; y <= 2000; y++)
	{
		if (y % 4 == 0)//1600进入
		{
			if (y % 100 != 0)//1600不进入
			{
				printf("%d ", y);
				count++;
			}
		}
		else if (y % 400 == 0)
		{
			printf("%d ", y);
			count++;
		}
	}
	printf("\ncount=%d\n", count);
	return 0;
}

这是因为闰年的判断规则有两个。1600走第一个if里的if走不了,但它又走了第一个if,不就行else if的判断。像这样就漏掉了第一种规则不符合,但跳过了第二种规则。

#include <stdio.h>
int main()
{
	int y = 0;
	int count = 0;
	for (y = 1000; y <= 2000; y++)
	{
		if (y % 4 == 0 && y % 100 != 0)
		{
			printf("%d ", y);
			count++;
		}
		else if (y % 400 == 0)
		{
			printf("%d ", y);
			count++;
		}
	}
	printf("\ncount=%d\n", count);
	return 0;
}

这种写法也是可以的但为了逻辑清新,建议还是两个if的好
在这里插入图片描述
还可以这样

#include <stdio.h>
int main()
{
	int y = 0;
	int count = 0;
	for (y = 1000; y <= 2000; y++)
	{
		if ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0))  //||或者
		{
			printf("%d ", y);
			count++;
		}
	}
	printf("\ncount=%d\n", count);
	return 0;
}

3.7.5给两个数,判断最大公约数

  1. 暴力求解
    假设这两个数是18和24,最小的是18
    在这里插入图片描述
#include <stdio.h>
int main()
{
	int m = 0;
	int n = 0;
	scanf("%d %d", &m, &n);
	int k = m > n ? n : m;    //把最小值赋给k
	while(1)
	{
		if (m % k == 0 && n % k == 0)
		{
			break;
		}
		k--;
	}
	printf("%d\n", k);
}
  1. 辗转相除法
    在这里插入图片描述
#include <stdio.h>
int main()
{
	int m = 0;
	int n = 0;
	scanf("%d %d", &m, &n);
	int k = 0;
	while(k = m % n)
	{
		m = n;
		n = k;
	}
	printf("%d\n", n);
}

前面输入小的值也是可以的
在这里插入图片描述

4.总结

这篇博客也是写了很久,太懒了。4月底开始写的,5月份开始没学了,直到7月放假。希望别我一样懒。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值