前言
作者写本篇文章旨在将自己在学习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控制语句
- c语言是一门结构化的程序设计语言。因为它支持三种结构:
顺序结构:从头走到尾。
选择结构(分支语句):选择一项,而舍弃其他
循环结构(循环语句):用于循环 - 控制语句
用于控制程序流程,以实现程序的各种结构方式。共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多分支
- 多分支
if(表达式1)
语句1;
else if(表达式2)
语句2;
else
语句3;
表达式1成立,执行语句1;
表达式1不成立,表达式2成立,只执行语句2;
表达式1、2都不成立,执行语句3。
哪真走哪,并且剩余判断不进行。
- c语言中表达式的书写
age为10,但依然输出青年。因为18<=age<28
书写错误
计算机不能像数学一样连着写,需要用到&&(逻辑且)
18<=age && age<28
2.多分支语句的实现
3.if语句的嵌套
这种写法和上面多分支2的写法效果一致。
2.1.4if语句的一些细节
- 多条语句的执行
要执行多条语句,必须使用代码块({}),也叫花括号或者叫大括号。
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
if(condition){
return x;
}
return y;
condition成立返回return x,条件不成立才能返回y。因为如果返回了x,y就没有机会了(return只要返回,程序结束,后面代码不执行)。而不是成不成立都返回y。这种书写很容易产生误解。
- 代码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之间的奇数
- 判断一个数是否为奇数。
什么是奇数?不能被2整除的数。所以只需要取余看是否不为0,不为0就是奇数
- 输出1~100之间的奇数
结合前面的判断,只需要产生1~100之间的数,每次判断一下就可以了。
当然也可以去掉判断部分。我们要输出的是1~100之间的奇数,1是奇数,那么直接从1开始打印,每次加2,连判断都不用了。
2.2switch语句
switch也是一种分支语句。常用于多分支,switch可以嵌套使用。(内部可出现if语句,continue不行)
switch(整型表达式)
{
case (整型常量表达式1):
语句1;
caee (整型常量表达式2);
语句2;
......
}
必须是整型,float、char等报错。
- switch语句入口:case
switch的整型表达式是几,它就从case几进去
输入7,从case 7进入。再尝试输入一下4
确实从case 4进去,但为什么还有星期五六七?这是因为,case是switch语句的入口,那你进去了,总得有个出口吧。没有出口就会导致程序从case 4开始一直玩下走,不会跳出。 - switch语句出口:break
在case语句后面加上break就能跳出switch语句,下面的不再执行。
case 4进去,输出星期四,遇到break,直接结束switch语句,跳出switch。所以要精准输出星期几,要在每一个case后面都加上break
switch后面的表达式是几,就从入口case几进入。遇到break则跳出。 - switc和case的括号内的表达式
switch(整型表达式):长短整型,int类型。可以是变量。
case (整型常量表达式):整型常量的表达式。1+0也算,但是整个表达式的结果只能是整型常量。 - 什么时候用break
并不是说每一个case后面都得加break,要看情况。比如说,星期一到星期五是工作日,周末是休息日,就可以这样。
所以并不是每一个case后面都要break,当多条case语句要执行同样的代码的时候,就可以把他们写在一起。所以具体情况具体分析。 - 编程好习惯
csae7没有break,也会自动跳出switch,是可以不用break也能跳出的,但还是建议加。这是因为,当有一天你或者别的程序员,现在要处理的不只是1-7,而是多了一个8这样一种情况。而case 8需要处理的情况和case 7不一致。这个时候要是直接加上去,那么case8和case7就会有逻辑矛盾。输入是7,8的代码也会执行。所以末尾加上break是为了后人(也是自己)着想,这也是一种编程的好习惯。 - default默认
前面说你输入的是1-7才能输出,那输入了9呢?什么也没有,因为匹配不了。default就是解决这种情况的
当输入的值都没有一个能与之匹配的case的话,它会走默认的default语句,里面我们可以放一点提示,提示你输入错误。当然里面也可以什么都不写。
default语句是用来处理非法状况的,可有可无。它的位置在switch哪里都行,建议放在末尾。
3.循环语句
上大学时,你每天买彩票想要一夜暴富,没中就老实学习找个好工作。就这样买彩票,没中,学习,买彩票,没中,学习。
突然有一天,你中了999万,那还学个毛线啊。循环就终止,不学习了,去结婚了。或者当你天天学习,技术达到一定的地步了,成为了大牛,也终止循环,不学习了,找到了一份好工作结婚去了。
我们每天做事情,就叫做循环。当循环满足某种条件的时候,不再继续循环,循环终止。
- while循环
- for循环(常用)
- do…while循环(必须循环一次)
3.1while循环
3.1.1if语句与while语句
- if语句和while语句非常相似
if语句前面学过,(1)为真,所以打印haha。
()的判断部分为真,也是进入while语句里,打印haha。而打印haha结束后,程序会再次回到while的判断部分。因为一直为1,恒为真。所以死循环打印haha。
- 示例
用while循环打印1-10的数字
while循环执行的逻辑是这样的
(1)先判断是否能进入。1<=10为真进入。
(2)打印完来到调整,n++就是调整,n+1后变成2,此时本次循环已经结束。
(3)再次来到判断部分。2<=10,还是符合。此后一直进行这样的循环。n加到10后。n<=10为真,打印10,n++变成11。11小于等于10不成立,所以循环终止
- 循环的三个部分
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假,跳出循环。
- break终止循环(整个循环终止)
当循环体中遇到break后,整个循环会被终止
n初始为1,不满足if(n==5)
,直到打印完4后,n++变为5。if(n==5)
条件为真,执行break,使循环直接结束,来到这里。
要注意switch和循环中的break都只能跳出一层,并且是它所在的那层 - 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实例
- getchar
getchar接收一个字符,接收成功,返回该字符的ASCII码值。接收失败,则返回EOF,也就是-1。要么返回ASCII码值,要么返回-1。因此它的返回类型是int,而不是char。
因为getchar没有参数,都是用返回值来接受字符的。所以getchar的使用方式一般都是用int ch = getchar();
功能和scanf很相似的,只是getchar专门用来处理字符,而且只能输入一个字符。 - putchar打印一个字符
只能打印字符,不能用来打印整型。想要打印什么就把它或者变量放在括号里
- EOF文件结束标志
end of file 隐藏在文件的末尾,返回EOF则返回-1
#define EOF -1 - getchar示例
作用是读一个字符就打印一个字符,直到getchar读取失败,返回EOF,即-1,使得while的判断部分wield假,跳出循环。当然在这里输入EOF是结束不了的。因为getchar是一个个读,实际上是分别读字母E,O,F。
ctrl+z才能结束。它的作用是让scanf或getchar直接返回EOF - 输入缓冲区
给上一段模拟用户登录的代码
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
- 只打印数字,而跳过其他字符
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循环
- while循环的三个部分
int main()
{
int i = 1;//1.初始化
while (i <= 10)//2.判断
{
printf("%d ", i);
i++;//3.调整
}
return 0;
}
每个部分改变都有可能会影响整个循环。未来,这个代码越来越庞杂的时候,这三个部分会越来越分散。而当你又要修改这某个部分的时候,你要兼顾其他两个。容易出错,因此while循环是不够理想的。for循环则整合了三个部分应运而生。
- for循环
for(表达式1;表达式2;表达式3)
循环语句;
表达式1为初始化,用于初始化循环变量。
表达式2为条件判断,用于判断循环何时终止。
表达式3为调整,用于循环条件的调整。
用for循环打印1-10。
- for循环的执行流程
先初始化;在判断是否符合。符合,则执行循环体。否则,直接跳出;
循环体执行结束,来到调整;调整完后,又回到2,判断是否符合。
一直这样在234,234之间循环,直到某一次调整后,不符合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循环则看调整的位置。 - for循环的三个部分不要轻易省略
当i++放到循环体内时
因此建议不要在for语句的循环体内修改循环变量,防止for循环失去控制。 - 建议for语句的循环控制变量的取值采用“前开后闭区间”写法
当然也可以写成i<=9
,但如果是前开后闭,那么这个10就有了某种意义。10次循环,10次打印。当然是这一般建议,也不是说所有的for循环都这么写,视情况而定。 - for循环的三个部分的省略
for循环的三个部分是可以省略不写的,但建议写。否则容易出错。
初始化和调整部分省略,啥也不做。
判断部分省略,意味着判断恒为真。
啥也不写后,判断恒为真,死循环打印hehe。
int i=0;
和int j=0;
已经有了声明,把它省略。但省略后,原来打印的是16个haha,省略初始化后只剩下4个
所以说不是特别熟练不要轻易省略任何一个部分。 - for循环变种
int x,y;
for (x = 0, y = 0; x < 2 && y < 5; ++x, y++)
{
printf ("hehe\n");
}
初始化是可以有多个变量的,也可以是表达式,甚至不是变量,可能是一次函数调用。
判断和调整部分也是可以有多个的
- 例题
问这个循环要循环多少次?
#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为假,这个循环压根就不进去
- 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循环
- 基本形式
do
{
循环体;
} while (判断);
- do…while特点
不管怎样,至少执行一次,适用于至少要执行一次循环的情况。 - do…while简单例子
- do…while循环执行流程
- do…while循环的break和continue
break永久终止循环
continue跳过本次循环后面的代码,和while循环的类似。要注意continue和调整的位置。
当continue在调整前时,造成死循环。
3.4练习
3.4.1计算n的阶乘(忽略溢出)
- 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。
- 二分查找胎教原理
你可能会想到这种算法
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次,但是一个个找,比较费电脑。
所以说用二分查找来做这道题,将会使代码得到非常大的优化。
- 二分查找的实现
上来就干,写一个框架
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,一定会打印“找不到”。就会导致,找到了,又找不到。因此要限定它的条件。
- 当然上述
mid=(left+right)/2;
存在缺陷
一个整型的范围大概在-21亿~21亿。当left和right很大时,但没有超过int的范围,但它们加在一起就超过了int的范围,就会发生截断。
所以我们可以这样写
当然还有其他多种写法
3.4.4编写代码,演示多个字符从两端移动,向中间汇聚
- 原理
其实要达到这种效果,只要把第二行的第一个*改成h,最后一个*改成!。第二次再把第二个*改成e,倒数第二个*改成!。就这样一直改,改成到最后*全部没有了。就做到了这种效果。改的位置是对应的,那么我给上左右下标。
此时只要将arr1[left]赋值给arr2[left],arr1[right]赋值给arr2[right],不就做到了交换吗?然后left右移,right左移,再赋值,又改掉一对。如此循环,最终得到演示的效果。
- 实现
首先定义数组
#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编写代码实现,模拟用户登录情景。
只允许输入三次密码,如果密码正确则提示登录成功,如果三次均输入错误,则退出程序
- 原理
三次输入密码,也就是循环咯。上来就干,给上循环。
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猜数字
实现一个猜数字游戏
- 电脑随机生产一个数
- 猜数字
a>猜大了,提醒猜大了,继续猜
b>猜小了,提醒猜小了,继续猜
c>猜对了,结束这把游戏 - 玩一把,不够,继续玩
3.5.1分析
要做的事有四件
游戏得有个菜单,打印它
产生随机数;
猜随机数,猜大了或猜小了有提示,继续猜。猜对了结束这把游戏
玩完一把,还不够,选择是否继续玩
- 打印菜单
选择用函数来封装,使得看起来更简洁
#include <stdio.h>
void menu() //只需要打印,不需要返回值,无参
{
printf("****************************\n");
printf("******1.play 0.exit********\n");
printf("****************************\n");
}
int main()
{
menu();
return 0;
}
- 选择玩游戏还是退出程序,两种情况,分支语句
#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;
}
- 每次选择,玩游戏还是退出,有多次,所以从菜单到退出是个循环体。至少玩一把,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;
}
- 判断部分,输入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;
}
- 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)
//处理特殊情况
- 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了。
- 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;
}
- goto语句只能在一个函数内部跳转,不能跨函数跳转
- 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;
}
每次输入错误,都给机会重新输入
这里已经开始等待关机,那么只要输入我是大笨猪就可以取消关机了
只有输入我是大笨猪,才会取消关机。
- 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都不是素数。
思路清楚之后就开始考虑怎么写代码了
- 首先要判断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
偶数能被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;
}
- 优化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整除的闰年
- 解
首先得有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给两个数,判断最大公约数
- 暴力求解
假设这两个数是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);
}
- 辗转相除法
#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月放假。希望别我一样懒。