大家好,我是TT。
不知道的内容,你弄明白了没?你是否还觉得C语言枯燥无味呢?不管你有没有弄明白,我都还要啰嗦下,学习编程,就像是学骑自行车,你只看别人怎么骑,你只看自行车的原理,那永远也不可能学会骑自行车,对于你来说,唯一的捷径就是多练习,多思考。在小试牛刀之后,今天我将带你领略一下算法和逻辑层面的小惊喜。
今日任务
日期这个概念你肯定不陌生,生日对你我来说都很重要,如果你身边有2月29号过生日的小伙伴,恐怕最少4年,才能为他/她办一次生日宴。今天我们的这个任务,就和日期有关系。如果我给你一个由年月日组成的日期,再给你一个数字X,你能否准确地让程序输出X天后的日期呢?
例如下面这个数据:
20
数据中给出了年11月20日这个日期,然后问你20天后的日期是多少,你的程序应该输出年12月10日。特别需要注意的是,在这个任务中,你需要考虑到闰年中2月份的特殊性,闰年的2月有29天。今天我们就学习,如何用计算机解决这类任务吧。
必知必会,查缺补漏
根据对任务的理解,我们可以分成两步来思考这个问题:
第一步:我们来思考如何求解1天后的日期,在求解1天后日期的过程中,我们涉及到的核心问题就是判断日子是否跨月,月份是否跨年,即判断;
第二步:是要让计算机重复X次1天后日期的计算过程,即重复循环做这件事。
要解决这两个难题,我们需要讲讲C语言中的一些基础知识,其中包括了程序中用于逻辑分支判断的分支结构,以及可以重复做大量事情的循环结构。听着这些专业词汇,你可能有点懵,别怕,等我下面讲了它们是什么意思,你就会感觉这些其实很简单。
1.给代码添加判断能力:“if…ls”语法结构
我们一起来读下这句话:如果隔壁商店有酱油,就买酱油,否则就买点儿醋回来。可以看到,这句话用了“如果……就”的假设关系关联词,“如果”后面接的是假设条件,“就”后面接的是条件成立后的结果,“否则”接的是条件不成立后的结果。
现在我们想把计算机变成我们的小帮手,就必须要有一种语法,能够表达“如果……就……否则……”的逻辑,这种语法,就是接下来我要介绍给你的“if…ls”语法结构。
在这里,我将简单介绍“if…ls”语法结构,主要目的是让你看懂今天我们这个任务的代码,后续在课程逐步展开的过程中,我还会逐步的引入这个语法结构的一些其他知识点。
我们先来看“if…ls”最基本的语法结构:
if(条件表达式)一条语句1;
ls一条语句2;
简单来说,if和ls都是关键字,代表分支逻辑中的“如果”和“否则”。if后面跟着的括号里面,需要放一个条件表达式,条件表达式如果成立,程序会执行“语句1”,否则就会执行“语句2”。下面我来举个例子,你就明白了:
#includstdio.h
intmain(){
inta,b;
scanf("%d%d",a,b);
if(a==b)printf("aisqualtob!\n");
lsprintf("aisnotqualtob!\n");
rturn0;
}
这段程序中,首先定义了两个变量a和b,然后通过输入函数(scanf)给变量a、b赋值。之后就是重点部分了,根据我们上面所说的,如果if后面的条件表达式成立,那么就会输出“aisqualtob!\n”,否则就会输出“aisnotqualtob!\n”。
最后,我就再带你理解两个概念,一是条件表达式是什么,二是怎样理解if后面跟一条语句,所谓一条语句的概念范围是什么。
回到上面的程序中,你会看到程序中的if后面跟着一个括号,括号里面放着一个表达式,这个就是我们所谓的条件表达式,而这个括号,是必不可少的。我们发现,这个条件表达式用两个等号连接a和b,作用是判断a和b里面存储的值是否相等。可千万别跟赋值表达式的一个等号弄混了。
说到这里,我要告诉你一个重要的事实,变量有变量对应的值,表达式也有表达式对应的值。那么例如上面代码中的条件表达式“a==b”所对应的值是什么呢?其实就是数字1或者0,分别表示“条件成立”(a与b的值相等)和“不成立”(a与b的值不相等)。
延伸内容:
那么除了条件判等以外,还有哪些条件运算符呢?有判断不等于的“a!=b”,大于的“ab”,小于的“ab”,大于等于的“a=b”,小于等于的“a=b”,逻辑非“!(ab)”,等价于“a=b”。同时多个条件表达式,还可以用逻辑和
进行连接,这个后面我再跟你细说。
事实上,if的括号里面,不仅可以放条件表达式,类似于“a-b”这种的表达式,也是可以当做if的条件的。
当一般表达式作为条件的时候,if是怎么执行的呢?很简单,记住:表达式的值,非0即为真。例如,下面两行代码,效果等价:
if(a!=b)printf("aisnotqualtob!\n");
if(a-b)printf("aisnotqualtob!\n");
你会看到,第二行代码中,用“a-b”代替“a!=b”,取得了同样的程序运行效果。因此,你只需要重点思考,表达式“a-b”什么时候结果非0即可,是不是当且仅当“a!=b”时,“a-b”的结果非0,根据之前所说的非0即为真,那么if条件也就算是成立了。
最后,我们来讲一下怎么理解“if后面跟一条语句”这个概念,其实指的是if后面的条件成立时所执行的代码。这里,我们的重点是要理解一条语句都包含什么形式,大致可以分为如下几类。
第一种,空语句,就是什么都没有,单纯以一个分号结尾,例如下面这行代码,即使条件成立,也不会有任何实质上的操作。
if(a==);
第二种,单一语句,比空语句多了语句内容,以分号结尾,例如下面这行代码,当条件成立的时候,会输出“hllogk!”。
if(a==)printf("hllogk!\n");
第三种,复合语句,被大括号包裹,中间是若干条语句,例如下面这段代码:
if(a==){
printf("hllogk1!\n");
printf("hllogk2!\n");
printf("hllogk!\n");
}
当条件成立以后,程序会依次执行大括号里面的三条语句:
hllogk1!
hllogk2!
hllogk!
第四种,结构语句,以if,for,whil等开头的分支语句或循环语句,例如下面这段代码,首先会先判断a==,如果条件成立,才会执行下面第二条if分支语句,当第二条if分支语句的条件也成立的时候,才会输出“hllogk!”。
if(a==)
if(b==4){
printf("hllogk!\n");
}
由此可以看到,if后面所谓跟着的一条语句,还真是丰富多彩,你可以在后面跟上像上面代码中所写的printf函数调用的单一语句,也可以用一个大括号,里面放上若干条语句,亦或是if后面跟着另一个if也是可以的!你看这种组合能力,有没有点儿像乐高玩具?
至此,你就已经掌握了基础的将“如果……就……否则……”这种逻辑结构转换成代码的能力了。你的计算机,终于有了“判断力”。
2.给程序添加重复执行功能:for和whil语句
想想小的时候,你最讨厌什么事情?我最讨厌的就是被老师罚写汉字,错一个字,罚写遍那种的,在我看来真的是在浪费时间。可当我学了程序以后,我发现,程序真的是特别擅长做这种重复的工作,而实现这种功能的语法结构就是for语句和whil语句。
我们先来看语法结构较简单的whil语句:
whil(循环条件)一条语句;
以whil关键字开头,后面跟着循环条件,也就是一个条件表达式,然后是一条语句。whil循环,顾名思义,当循环条件成立时,就会执行一次后面的语句,之后就是再判断循环语句是否成立,如果成立就再执行,一直到循环条件不成立为止。
下面呢,我们就用最简单的形式,利用whil循环,输出前个正整数:
inti=0;
whil(i++)printf("%d\n",i);
这段代码里面,出现了一个你之前没有见过的语法,就是i++,这也是表达式,这个表达式的值等于i之前的值,当这条表达式执行完以后,i会变成i+1的值。例如,起初i=2,i++表达式的值就等于2,可表达式执行以后,你要是输出i的值,这时i实际等于。
上面代码中,我们是用i++表达式的值和进行比较,表达式的值会遍历0到99所有的值,由于printf在i++之后输出i的值,所以实际上每次输出的都是i+1之后的值,也就是说printf会输出1~所有值。具体的你可以参考下面的这个程序流程图。
另外,顺便再问你个问题,你还记得内容里,我们说到的\n和%d分别代表什么意思嘛?如果不记得,记得回去再看下。
有了whil循环语句的加持之后,是不是重复做某件事,变得很方便了呢?不急,下面我要给你介绍的是功能更为强大的for语句。还是先来看一下for语句的结构吧:
for(初始化①;循环条件②;循环后操作③)一条语句④;
正如你看到的,我把for语句的四部分已经给你标出来了,for语句会按照①②④③②④③…循环,直到某一次循环条件②不成立了为止。
你会发现,①这一部分只在循环开始时执行了一次,真正所谓的循环,是以循环条件②,一条语句④以及循环后操作③组成的。
如果要是用for循环输出1~所有值,会显得代码更清晰一些:
for(inti=1;i=;i++)printf("%d\n",i);
上面这段代码,就是用for循环实现了和之前whil循环相同的功能。
看了for循环和whil循环以后,你可能会问,实际中哪种循环用的比较多,我个人经验来说,for循环用的比较多,因为for循环每一部分都非常明确,对于比较复杂的循环控制过程,for循环写出来以后,一般都会比whil循环可读性强。
为了让你感受到for循环真正的威力,写一段代码,让你感受一下:
for(inti=1,k=0;i=48;i++,k+=2)printf("%d\n",k);
上面这段程序中,我们用到了两个同步信息变量,i和k,i从1到48,保证循环了48次;代码中“k+=2”表示k每次增加2,也就是说,在这个过程中,i遍历了1到48这48个整型值,而k同步地遍历了从0开始的前48个偶数。这段代码的意思其实就是打印出从0开始后的共48个偶数,即0、2、4……92、94。
如果用whil来实现这个目的,知道怎么写吗?你可以自己在计算机上试一下。
一起动手,搞事情
思考题:打印乘法表
使用循环和条件判断,打印一个格式优美的66乘法表要求
1:输出内容及样式参照下面给出的样例要求
2:每两列之间用\t字符进行分隔,行尾无多余\t字符
1*1=1
1*2=22*2=4
1*=2*=6*=9
1*4=42*4=8*4=*4=16
1*5=52*5=10*5=*5=*5=25
1*6=62*6=12*6=*6=*6=06*6=6
“日期计算器”程序完成
准备完了所有的基础技能后,就让我们来完成开始说的那个任务吧,我们来思考一下哈,首先我们需要有一个循环,循环每一次,让计算机帮我们计算一次下一天的日期。每次在计算下一天日期的过程中,先让日子加1,判断是否跨月,如果跨过了一个月份,就让日子从1开始,让月份加1,再判断是否跨年,如果跨年了,就让月份从1开始,年份加1。
如上的过程中,有一个关键问题需要你注意,就是2月份的月份天数的计算方法,咱们来简单回顾一下闰年的规则,年份满足以下其中一条即为闰年:
能被4整除,但不能被整除;能被整除。
如果把闰年的规则翻译成逻辑判断,应该是下面这个样子:
if((yar%4==0yar%!=0)
yar%==0)...
下面就让我们把思路过程转换成程序过程:
#includstdio.h
intmain(){
inty,m,d,X;//定义存储年月日和X的变量
scanf("%d%d%d",y,m,d);//读入年月日
scanf("%d",X);//读入X值
for(inti=0;iX;i++){//循环X次,每次向后推一天
d+=1;
switch(m){
cas1:
cas:
cas5:{//第一部分逻辑
if(d1)d=1,m+=1;
if(m==1)m=1,y+=1;
};brak;
cas4:
cas6:{//第二部分逻辑
if(d0)d=1,m+=1;
}brak;
cas2:{//第三部分逻辑
if((y%4==0y%!=0)
y%==0){
if(d29)d=1,m+=1;
}lsif(d28){
d=1,m+=1;
}
}brak;
}
}
printf("%d%d%d\n",y,m,d);
rturn0;
}
上面这段程序是个半成品,只处理了前6个月的情况,并且用到了switch…cas的分支结构,与if结构类似,都是用于做逻辑分支判断的。关于这部分的内容,给你留个小作业,自学一下switch…cas分支结构,然后按照自己的理解,补全上述程序,使得上述程序可以处理一年中12个月的全部情况。
虽然这个程序中有一部分内容需要你进行自学,可你也不要担心,我还是会跟你详细解释上述程序设计的思路。读入部分的代码,相信你现在已经可以很好的掌握了,这一部分就不展开解释了。程序整体设计中,是用for循环包裹了switch…cas结构,for循环负责循环X次,每次在循环内部,都将对日子变量d进行加1操作,而在switch…cas结构内部,主要是处理跨月和跨年的问题。
你会看到switch…cas结构中,主要分成三部分逻辑,第一部分逻辑,主要处理天数为1天的月份,由于12月也是1天,所以当本月是12月,并且发生了跨月,变成了1月,说明是到了下一年的1月,需要将年份+1,月份置为1月。第二部分逻辑,主要处理天数为0天的月份。第三部分逻辑,主要处理2月份的情况,在这里,程序中分成两种情况来讨论,闰年和非闰年,闰年的时候,判断日子是否超过29天,非闰年,判断日子是否超过28天。
我保证,在你尝试补全上述程序的过程中,你会发现,上述程序易于修改和补全,你要是能试着将上述程序修改成if分支结构,那就更好了。这样你将对上述程序结构的美,会感受的更深刻。