第四课 如何开发一个万年历---上篇

第四课 如何开发一个万年历----上篇


        万年历程序是个有效的体验项目,它包含了程序开发所需要的技术和方法,甚至体现了很多软件工程的内容,项目要求输入某年、某月的整型数字,输出那年那月的月历,例如输入 2015 年 5月,项目应该打印正确的包含星期的月历。




         万年历项目包括了开发程序的所有基本语法内容(除了自定义数据),这对掌握基本语法很有益,另外重要的是万年历项目的开发方法,见过许多人学和用这个例程,但是用明白的不多,嘿嘿,招式可以学,吐纳呼吸的方法没人讲那是不容易学得会的呀。还有一个项目和万年历一样,但稍微高阶一些,本节课的后面会稍微讲解一下关键,然后让大家自己实践,那就是一个扑克游戏“二十一点”。是否掌握了在一个操作系统上编程的方法或者是否掌握了一门语言,那么尝试写一个万年历和一个二十一点游戏吧。


本项目涉及的知识有:

1、变量定义

2、输入与格式化输出

3、函数

4、关系运算逻辑运算

5、集合:列表

6、程序流程:分支与循环

7、多文件机制


        编程只有目标没有思路,这句话是我说的,但是漫漫开发路上总要给自己设立几个路标,就如同有个著名马拉松运动员说的那样,把漫漫长路分成几个可以达到的目标很有助于树立信心。


        
        编程如雕塑,不象书法,“一蹴而就”基本不可能,开发就是设立一个大体目标,分解成阶段目标,然后修修补补,咋看起来,如何开发万年历程序是个“不摸门”的事,但是还是有几个可以达到的目标的:万年历程序开发的过程大约设定为5步,


第一阶段:形式月历,目标是输出一个“死的”月历

第二阶段:输入年月,确定每月应该输出多少天?

第三阶段:确定月历中的一号是星期几,然后输出月历

第四阶段:打印当年的任意月历(年历)

第五阶段:由你完成万年历



        第一阶段是项目完成的大致形式,但是没有任何逻辑,全部用打印完成,所以比较容易。以后每阶段都在以前的基础上前进了一小步,但每一阶段都是完整的,每一阶段都有一个另开发者感兴趣的结果。这就是软件开发中的“迭代增量”。开发这个项目时千万不要找个完整的万年历程序试图“读懂”,根本没有意义,一定要尝试并体会项目“生长”的过程。


        下面就来开发第一阶段,你会发现,即使是一个阶段目标,也会有两三次迭代


第一阶段:形式月历,目标是输出一个“死的”月历,如下图




        
        
        
        我想先打印这样一个“样子”不难吧:关键在于需要将所有输出内容组成一个“字符串”



        于是就先写了个这个例程1-1:

out_str= "  SUN    MON    TUE    WEN    THU    FRI    SAT  "

i=1

for i in range(1,31):

out_str=out_str+`i`


print out_str


结果打印成这样:





   

        所有30天的数字挤在一起了,想一想,应该插入一些空格,然后逢7插入一个“回车”,这里的难点是利用好第一个被整除的数字:“0”

于是程序变成了这样,例程1-2:


out_str= "  SUN    MON    TUE    WEN    THU    FRI    SAT  "

i=0

for i in range(32):

    if i%7==0:

        out_str=out_str+'\n   '+`i+1`

    else:

        out_str=out_str +"   "+`i+1`


print out_str



运行发现,输出变成了这样:





        
        
        
        


        很接近目标了,只是输出无法对齐,



        依靠程序中字符串加入的空格是很难“对齐”的,这时候就用上“字符串的格式化输出了”,代码如例程1-3:


out_str= "  SUN    MON    TUE    WEN    THU    FRI    SAT  "

i=0

for i in range(31):

    if i%7==0:

        out_str=out_str+'\n{:^7d}'.format(i+1)

    else:

        out_str=out_str +'{:^7d}'.format(i+1)


print out_str


运行结果如下图





字符串的格式化输出需要了解的要点



1)格式:字符串.format( 表达式 )

2)字符串中加如形如{:^nd}的格式控制符,用以控制format中表达式的输出形式

3)常用格式控制符有:{:^m.nd}用来输出整形,n代表输出所占字符列数;{:^m.nf}用来输出浮点型,m代表输出所占字符列数,n代表小数点部分的位数。

4)若指定输出的字符列数大于实际输出,则输出空格占位;若实际输出字符数大于指定,则按实际输出。

至此,第一阶段完成了!




        
        
        


        第二阶段:输入年月,确定每月应该输出多少天?

每月输出多少天是由年和月共同决定的,比如闰年的2月就该输出29天

所以第二阶段的第一步比较简单,把原来循环里那个固定值“31”变成一个变量,所以程序变成了例程2-1的样子:

例程2-1源文件和运行的结果




        
        


        在例程2-1的第5行,增加了变量days_i_m,在第7行它代替31控制了“打印天数”,那么,只要控制days_i_m就能控制“打印天数”了,在实际用应用中这个”打印天数“应该和月关联。于是,代码变成了例程2-2所示:






        注意例程2-2的 12到17行 利用一系列的if elif和else 确定了输入的month和打印天数的关系,但是这个结构不够巧妙,资深程序员的经常考虑的问题就是减少程序中的if,特别是象“排比”一样的if使人眼晕。


         有一句名言:程序=算法+数据结构,这句话固然说明了程序由算法和数据结构组成,另外更关键的含义是算法和数据结构之间是“和的关系”,这意味着:数据结构恰当,算法就可以简单一些。所以我们可以把每个月的天数组织起来放入一个元组(数据不可变的列表)中:

days_month=(0,31,28,31,30,31,30,31,31,30,31,30,31),由于元组的下标从0开始,而月份从1开始,所以在元组的0元素位置使用0占个位置,这样打印天数就简单地变为:

days_month[month]了,省略了一大堆if,其实只要计算得当,内存充裕,任何一个程序的任何一个if都是可以被优化的(为什么和if有那么大“仇恨”?那是因为if占用资源较多)。

例程2-2还有一个不妥之处,那就是对“闰年2月”没做处理,另外如果能把一些判断和求值都“工具画”就好了,于是想写两个函数,一个判断是否是闰年is_leap_year,另一个求输入的月份有多少天 days_in_month,为了使源文件结构更清晰,应该把工具性的函数都放到另一个新的源文件中去,这样还可以利于今后的复用,于是例程2-3的两个源文件如下:

例程2-3







        例程2-3包含两个函数的工具包文件: can_2_3_tool.py,这个文件包含了两个函数,其中is_leap_year用来计算输入的年份是否是闰年,第7行定义了常规每月包含天数的序列,第10行是闰年的判断条件,从第11行和13行得知若参数y是闰年函数将返回True否则返回False;函数days_in_month的用途是根据参数y(代表year),m(代表month)来计算指定的月份应该有(打印)多少天,之所以计算每月的天数时需要参数y,那是因为需要考虑一个特例:闰年的2月有29天。在days_in_month函数中调用了is_leap_year来确定输入的年份是否是闰年。


        由于使用了函数,calendar2_3的主要源程序文件就变得比较简洁易懂了,第2行,利用from和import引入can_2_3_tool.py中的函数,第7,8行输入了年和月,第17行直接利用函数days_in_month计算出了每月需要打印多少天,并赋值给print_days,应该注意到了,程序的第15行定义了一个变量wd,这个变量的将用来控制每月1日打印位置(或者说每月1日对应周几?),现在我们把它赋初值为0,并且不改变它,那么程序像以前一样打印并没有变化,后面就是顺理成章了。



        程序运行的结果如下:




结果表明第二阶段也成功了。





        
        
        
        
        
        
        
        
        
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值