题目要求

编程实现万年历,要求:

可根据用户输入或系统日期进行初始化,如果用户无输入则显示系统日期所在月份的月历,并突出显示当前日期;

可根据用户输入的日期查询,并显示查询结果所在月份的月历,突出显示当前日期,并提示是否闰年

对任何不合法输入数据,拒绝查询并进行提示。

细节提示

可将思考、编程划分为以下几个模块:

  1. 如何通过已有日期和星期推算要求的日期的星期?
  2. 如何整齐地输出月历?
  3. 如何获取系统时间?
  4. 在有余力的前提下,如何美化界面?

下面对上面的几个问题给出粗略的概述。

具体实现和技巧性地东西参考后文代码。

问题1 日期推算

众所周知,需要推算日期的模拟题都是毒瘤题

日期推算的算法有很多,这里只给出我的思路:

  1. 推出差了多少天。
  2. 用数学公式推出星期。

这条公式是 \((w+d) \mod 7\),d 表示差的天数,w 表示原本是星期几。

我采用的是标准的 0 表示 Sun. 而 6 表示 Sat. 的方法。

time.h 自带的 tm_wday 就是用这种方式表示的。

需要注意的是 C 与 C++ 对负数取模的特(sha)殊(bi)性,所以为了求出正确的结果,我们要采用一点小技巧。

if(w1+d<0) w2=(w1+d)+(-w1-d)/7*7+7;

似乎也可以在推出天数后乘上86400减一下然后扔给 localtime() 去推星期。

但是你连天数都推出来了,直接算不香吗。而且既然是万年历,秒数太大爆了怎么办

接下来让我们考虑如何推算差了多少天。

我为了方便计算,所有的推算都以2020年1月1日星期三为基准。

由一个基准来推的化可以省去很多麻烦。

首先,第一种方法是暴力模拟。一年一年地推、一月一月地推、一天一天地推。

我在代码中注释掉的就是暴力模拟法。

这个没什么好讲的,闰年就差 366 天,否则差 365 天。

年推到了就推月,实现把每个月份的天数打个表,别忘了特判二月就行。

你也可以不像我那样偷懒一个一个月推,使用前缀和数组+闰年特判也行。但是每次查询最多就推 12 个月,一个月一个月推也差不了多少。

这点时间肉眼是看不出来的。所以随便吧。

天数就没什么好说的,自己随便想两个同年同月的日期看看差几天,很快就能看出是直接拿日期相减了。

其实,我们不难发现,年份可以不用一年一年模拟,可以用数学公式算。

现在我们要算 A年1月1日 到 B年1月1日 经过了几个闰年。

以 A < B 为例

直接拿 (B-A)/4 来算闰年个数这种玄学的事情我是不会干的。我希望求出的闰年个数是绝对准确的。

因此可以这样来:

我们知道 x/4 可以表示小于等于 x 的正整数中 4 的倍数的个数。

我们需要求经过的闰年的个数,只需要知道区间 [A,B-1] 中 4、100、400 的倍数的个数就行了。

( 因为我考虑的是 1月1日 ,如果考虑 12月31日 的话,应该变为 [A+1,B] )

根据容斥原理,记 4、100、400 的倍数的个数分别为 \(c_1,c_2,c_3\)

我们有:\(n = c_1 - c_2 + c_3\)

根据前缀和的思想,我们有:

\(c_1 = (B-1)/4 - (A-1)/4\)

应该不会有人看不懂前缀和吧,不过我还是解释一下吧。

因为 A 是包含在区间里面的,我们要求 [A,B-1] 的区间权值,自然不能把 A 删出去,所以要用 A-1 。

其它几项同理。

于是我们求出了闰年的个数,于是 \(d = (B-A) + n \times 1\)

至于 A > B 的情形,同理,只需要把区间改为 [B,A-1] 。

然后根据前缀和,你会发现式子是一样的,只是正负号变了而已,所以没有分类讨论的必要

这样就解决了最关键的问题,剩下的只需要动用知识和耐心去模拟就好了。

问题2 月历的格式

这个随便百度一下万年历或者点一下右下角的时间模仿一下它的格式就行了。这里介绍几个技巧。

分行 printf (这个好像谁都会)

char s[]="you bao da me.";
printf(
"I too vegetable le.\nI do not have %d pens.\n"
"You too strong le.\n%s\n"
"I also want as strong as No.%d.\n",5,s
);

对齐

利用 %-*d 可以靠左对齐,%*d 则是靠右对齐。

总之计算好需要的字符长度然后分配即可。看着不行多试几次。

利用字符数组减少工作量

char wday_[7][7]={"Sun. |","Mon. |","Tues.|","Wed. |","Thur.|","Fri. |","Sat.  "};
char div_line[]="============================================================";

需要注意的是,二维数组的字符串长度必须声明。因为只有知道了长度才可以分配内存。二维数组不止要分配第一个字符串的内存,还要同时按间隔分配余下的内存,不规定长度的话它不知道要在哪里放第二个。

(下面这个是我的个人理解,因为我一开始出了这个问题)

还有,不建议把字符数组的长度设得刚刚好。printf("%s",wday_[1]) 读入的只是 wday_[1] 的指针,而不知道 wday_[1] 到底有多长(因为二维数组的内存分配是连续的),确实我只用了六个字符 "Sun. |" 但是连在一起的话计算机眼中是这样的 "Sun. |Mon. |" 也就是说,因为连在一起,中间没有字符串终止的标记,%s 就会把你整个二维数组全输出来。多预留出至少一位就能解决这个问题。

另外,我发现 div_line[] 默认分配到是恰好的 61 个 char 的长度。也就是说这玩意后面也没有预留一位。那假如我在之后的某次操作中恰好用接在它后面空间声明了一个字符串 ss ,那我 printf("%s",div_line) 的时候是不是也会把 ss 输出来?

有点意思,这个问题先留个影,以后再研究吧。

问题3 <time.h>的简单用法

这个百度一堆,不做赘述。个人比较喜欢这篇

我在这里转载一段代码:

struct tm {
int tm_sec; /* 秒 – 取值区间为[0,59] */
int tm_min; /* 分 - 取值区间为[0,59] */
int tm_hour; /* 时 - 取值区间为[0,23] */
int tm_mday; /* 一个月中的日期 - 取值区间为[1,31] */
int tm_mon; /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */
int tm_year; /* 年份,其值等于实际年份减去1900 */
int tm_wday; /* 星期 – 取值区间为[0,6],其中0代表星期天,1代表星期一,以此类推 */
int tm_yday; /* 从每年的1月1日开始的天数 – 取值区间为[0,365],其中0代表1月1日,1代表1月2日,以此类推 */
int tm_isdst; /* 夏令时标识符,实行夏令时的时候,tm_isdst为正。不实行夏令时的进候,tm_isdst为0;不了解情况时,tm_isdst()为负。*/
};

需要注意的是 tm_year 返回的是差值,且 tm_mon 是从 0 开始的

直接放代码和注释。

#include <time.h>
#include <stdio.h>
int main(){
struct tm *t; /*因为下面用上的两个函数返回值都是指针*/
/*time_t 其实是整数,具体是 long 还是 int 之类的可能不太一样*/
time_t x;
/*使用 time 函数获取基准时间到现在时间经过的秒数 这有两种方法*/
time(&x);/*可以利用 time 改动指针 &x 对应的值*/
x=time(NULL); /*time 返回值也是秒数,所以这样写也行*/
/*NULL 也可以改成随便一个指针,但是这样一来那个指针对应的数会被修改,这需要注意*/
t=localtime(&x);/*获取x秒对应的本地时间(UTC+8)*/
t=gmtime(&x);/*也可以用这个函数,获取UTC标准时间*/
/*之后便可以用上面的结构体里的东西了*/
printf("Now is %d\n",t->tm_year+1900);
return 0;
}

问题4 美化

基于我对 cmd 界面的认识,我认为改动颜色可以使他更好看(雾

其实 lxy 大佬有向我介绍用 printf 改变字符串颜色的做法,但是看起来太麻烦了,我懒得弄,感兴趣的可以自己百度去试一试。

关于常见的 cmd 命令,可以在 cmd 窗口输入 help 去查,也可以用 "/?" 如 color /? 这样的命令去查询细节

使用 <stdlib.h> 中的 system 函数可以运行 cmd 命令。(大概吧)

分割线也挺好看的。嗯。挺好看的。(确信

当然你要卷 GUI 那当我没说过(逃

效果图:事实证明,每行留几个空格在前面会好看一点,不过我不太想改了。







这是一个方便快速跳过图片的标记 ~

顺便,无奖求 hack ,也许哪个日期的星期是错的。至少我现在没查出有错误。

哦对了,我担心有人的基准年不是 1900年 所以加了一个 Fix Mode

代码库

#include <time.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define PAUSE() system("pause")
#define CLEAR() system("cls")
#define rep(i,a,b) for(int i=a;i<=b;i++)
char wday_[7][7]={"Sun. |","Mon. |","Tues.|","Wed. |","Thur.|","Fri. |","Sat. "};
char div_line[]="============================================================";
char _16bas[]="0123456789abcdef";
void color_change(){
/*change the color of cmd in random*/
static char cmd_[] = "color f0";
cmd_[7]=_16bas[rand()%7];
system(cmd_);
}
void _statement(){
color_change();
printf(
"\n"
"============================================================\n"
"Welcome to use Permanent Calendar by Qing_!\n"
"Here, you can see the monthly calendar now.\n"
"Here, you can query the calendar for anyday.\n"
"Come on, study-human! Now, enjoy your time!\n"
"Notice: I will use Chinese English to talk with you.\n"
"============================================================\n"
"\n"
);
PAUSE(); CLEAR();
}
void put_space(int x){ while(x) x--,putchar(' '); }
void i_am_doing(){
/*To tell user that I'm calucating.*/
static int cc=0,p=0;
cc=(cc+1)%25; if(cc>0) return;
p=(p+1)%27; CLEAR();
printf("\n%s\nNow calucating\n",div_line);
rep(i,1,p) putchar('.'); putchar('\n');
printf("%s\n",div_line);
} /*----------------------------------------------------------------------------*/ struct DATE{ int year,mon,day,wday; };
int c_day[]={0,31,0,31,30,31,30,31,31,30,31,30,31};
int bas_Y=1900; void Fix_Mode(){
/* May be your bas_Y is not 1900. */
color_change(); CLEAR();
printf(
"%s\nHere is Fix-Mode.\n"
"This is an important step.\nPlease input a correct year.\n"
"Before use, input the year today like this:\n2020\n"
"To fix the base year of different system.\n%s\n",div_line,div_line
);
printf("The year today is:"),scanf("%d",&bas_Y);
time_t now; time(&now);
struct tm *t=localtime(&now);
bas_Y-=t->tm_year;
printf("Done. Press any key to see the change.\n");
PAUSE();
}
void input_date(struct DATE *A,int y,int m,int d,int w){
/* Maybe i havenot use this */
A->day=d; A->mon=m; A->year=y; A->wday=w;
}
void get_date(struct DATE *A,struct tm *t){
/* Notice: tm_year is a delta with 1900, tm_mon is [0,11] */
A->day=t->tm_mday; A->mon=t->tm_mon+1;
A->wday=t->tm_wday; A->year=t->tm_year+bas_Y;
}
int is_leap_year(int year){
return year%100==0 ? year%400==0 : year%4==0;
}
int legal_judge(struct DATE *Q){
if(Q->day<=0||Q->mon<=0) return 0;
if(Q->mon>12||Q->day>31) return 0;
if(Q->mon==2) return is_leap_year(Q->year)?Q->day<=29:Q->day<=28;
return Q->day<=c_day[Q->mon];
}
int get_wday(int wday,int delta){
wday+=delta;
return wday<0?wday-wday/7*7+7:wday%7;
}
int get_day(struct DATE *Q){
if(Q->mon==2) return is_leap_year(Q->year)?29:28;
return c_day[Q->mon];
}
void _display(struct DATE *Q){
/* To display the date. */
/* The head */
if(is_leap_year(Q->year)) printf("Do you know? %d is a leap year ~\n",Q->year);
else printf("Wuhu, i want to fly ~\n");
printf("Here: %d-%d\n",Q->year,Q->mon);
rep(i,0,6) printf("%s",wday_[i]); putchar('\n');
/* what day is it? */
int _wday=get_wday(Q->wday,-Q->day+1),mDAY=get_day(Q);
rep(i,0,_wday-1) put_space(2),putchar('/'),put_space(2),putchar('|');
rep(i,1,mDAY){
printf(i!=Q->day?" %2d ":"[%2d] ",i);
putchar(_wday==6?'\n':'|');
_wday=(_wday+1)%7;
}
if(_wday!=0){
rep(i,_wday,5) put_space(2),putchar('/'),put_space(2),putchar('|');
put_space(2),putchar('/');
}
putchar('\n');
}
void calc_wday(struct DATE *Q){
/* Base on 2020-1-1 Wed. */
int delta=0,by=2020,bm=1,bd=1;
/*
while(by<Q->year){
delta+=is_leap_year(by)?366:365;
by++; i_am_doing();
}
while(by>Q->year){
delta-=is_leap_year(by-1)?366:365;
by--; i_am_doing();
}
*/
delta+=(Q->year-by)*365;
delta+=((Q->year-1)/4-(by-1)/4);
delta-=((Q->year-1)/100-(by-1)/100);
delta+=((Q->year-1)/400-(by-1)/400);
by=Q->year; while(bm<Q->mon){
if(bm==2) delta+=is_leap_year(by)?29:28;
else delta+=c_day[bm];
bm++; i_am_doing();
}
delta+=Q->day-bd;
Q->wday=get_wday(3,delta);
}
void Query_Mode(){
color_change(); CLEAR();
printf(
"\n%s\nWelcome to Query-Mode!\n"
"In this mode, you can input a date like this:\n"
"1969 11 9\n"
"And I will show you the monthly calendar of the date.\n"
"Notice not to input an illegal date.\n"
"If, you do that, I may point it out.\n"
"When you want to exit this mode, input three \'0\':\n"
"0 0 0\n"
"Enjoy your time!\n%s\n\n",div_line,div_line
);
PAUSE();
struct DATE Q;
while(1){
color_change(); CLEAR();
printf("Now tell me what date you want to query:\n");
scanf("%d%d%d",&Q.year,&Q.mon,&Q.day);
if(Q.day==0&&Q.mon==0&&Q.year==0){
color_change(); CLEAR();
printf("\n%s\nThanks for your use!\n",div_line);
printf("Now press any key to exit Query_Mode.\n%s\n\n",div_line);
PAUSE(); return;
}
if(legal_judge(&Q)==0){
printf("You input an illegal date! Try again!\n");
PAUSE();
continue;
}else{
calc_wday(&Q); CLEAR();
/* display */
printf("%s\n",div_line);
_display(&Q);
printf("%s\n",div_line);
/* ask for another */
printf(
"I have show you the calendar.\n"
"Now press any key to come back.\n"
"If you want to exit this mode, input \'0 0 0\' next time.\n"
);
PAUSE();
}
} } /*----------------------------------------------------------------------------*/ int main(){
srand(time(NULL));
_statement();
while(1){
time_t sec_; time(&sec_);
struct tm *p; p=localtime(&sec_);
struct DATE now; get_date(&now,p);
/* Display the date today. */
color_change(); CLEAR();
printf("Today is a good day!\n");
printf("%s\n",div_line);
_display(&now);
printf("%s\n",div_line);
/* Ask for next option. */
printf(
"What do you want to do now?\n"
"Input an opt as follow to tell me.\n"
"1 - to query some date.\n"
"2 - to fix year.\n"
"3 - to exit.\n"
"If you input something else, \n"
"I will change the color for you.\n"
);
int opt;
printf("%s\nInput option:\n",div_line),scanf("%d",&opt);
if(opt==1) Query_Mode();
if(opt==2) Fix_Mode();
if(opt==3){
color_change(); CLEAR();
printf("%s\nSee you next time!\n%s\n",div_line,div_line);
PAUSE(); break;
}
}
return 0;
}

END

【实验课选题详解】用C语言实现万年历的更多相关文章

  1. 「实验课选题详解」用C语言实现万年历

    题目要求 编程实现万年历,要求: 可根据用户输入或系统日期进行初始化,如果用户无输入则显示系统日期所在月份的月历,并突出显示当前日期: 可根据用户输入的日期查询,并显示查询结果所在月份的月历,突出显示 ...

  2. VS2010 Chart控件(一)Chart控件在ASP.NET网站中的应用示例详解(C#语言)

    步骤如下: 1. Chart控件(一)Chart控件在ASP.NET网站中的应用示例详解(C#语言)" title="VS2010 Chart控件(一)Chart控件在ASP.NE ...

  3. 快速排序详解(C语言/python)

    快速排序详解 介绍: 快速排序于C. A. R. Hoare在1960年提出,是针对冒泡排序的一种改进.它每一次将需要排序的部分划分为俩个独立的部分,其中一个部分的数比的数都小.然后再按照这个方法对这 ...

  4. 【Golang详解】go语言中并发安全和锁

    go语言中并发安全和锁 首先可以先看看这篇文章,对锁有些了解 [锁]详解区分 互斥锁.⾃旋锁.读写锁.乐观锁.悲观锁 Mutex-互斥锁 Mutex 的实现主要借助了 CAS 指令 + 自旋 + 信号 ...

  5. 撩课-Mysql详解第3部分sql分类

    学习地址:[撩课-JavaWeb系列1之基础语法-前端基础][撩课-JavaWeb系列2之XML][撩课-JavaWeb系列3之MySQL][撩课-JavaWeb系列4之JDBC][撩课-JavaWe ...

  6. RSA算法详解及C语言实现

    RSA算法它是第一个既能用于数据加密也能用于数字签名的算法.它易于理解和操作,也很流行.算法的名字以发明者的名字命名:Ron Rivest, Adi Shamir 和Leonard Adleman.但 ...

  7. 链表详解(C语言)

    链表是一种常见的基础数据结构,结构体指针在这里得到了充分的利用. 链表可以动态的进行存储分配,也就是说,链表是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入 ...

  8. 【jsp】详解JSP表达式语言(EL)

    一.JSP EL语言定义 E L(Expression Language)  目的:为了使JSP写起来更加简单. 表达式语言的灵感来自于 ECMAScript 和 XPath 表达式语言,它提供了在 ...

  9. 撩课-MySQL详解1-数据库简介

    学习地址:[撩课-JavaWeb系列1之基础语法-前端基础][撩课-JavaWeb系列2之XML][撩课-JavaWeb系列3之MySQL][撩课-JavaWeb系列4之JDBC][撩课-JavaWe ...

随机推荐

  1. mybatis动态条件组合分页查询

    一.动态条件处理 需要使用mybatis的动态sql 1 <select id="selectItemByCondition" parameterType="com ...

  2. Java知识系统回顾整理01基础01第一个程序01JDK 安装

    一.首先第一步看JDK配置成功后的效果 点WIN键->运行(或者使用win+r) 输入cmd命令 输入java -version 注: -version是小写,不能使用大写,java后面有一个空 ...

  3. Docker---初识到使用

    1.剖析虚拟化技术概念 1)虚拟化是一个主流的技术,虚拟的,看不见的,但是可以落地的,用于资源管理的一项技术: 2)虚拟化的技术诞生的目标就是为了解决资源管理和资源利用的解决方案: 3)虚拟化就是将物 ...

  4. 【优化】单调队列与dp

    笔者大概看了一下单调队列对于DP的优化,故撰此文,望有帮助. (dp还是推式子难啊qwq) 例题1. 题目大意:在n个数的序列中,选择数字,使得其连续不超过k个数,且和最大. 本题的方程相对好推:设d ...

  5. ElasticSearch 索引 VS MySQL 索引

    前言 这段时间在维护产品的搜索功能,每次在管理台看到 elasticsearch 这么高效的查询效率我都很好奇他是如何做到的. 这甚至比在我本地使用 MySQL 通过主键的查询速度还快. 为此我搜索了 ...

  6. 加快ASP。NET Core WEB API应用程序。第3部分

    下载source from GitHub 对ASP进行深度重构和优化.NET Core WEB API应用程序代码 介绍 第1部分.创建一个测试的RESTful WEB API应用程序. 第2部分.增 ...

  7. NOIP提高组2016 D2T3 【愤怒的小鸟】

    貌似还没有写过状压DP的题目,嗯,刚好今天考了,就拿出来写一写吧. 题目大意: 额,比较懒,这次就不写了... 思路分析: 先教大家一种判断题目是不是状压DP的方法吧. 很简单,那就是--看数据范围! ...

  8. Java学习之动态代理篇

    Java学习之动态代理篇 0x00 前言 在后面的漏洞研究的学习中,必须要会的几个知识点.反射机制和动态代理机制.至于反射的前面已经讲到过了,这里就不做更多的赘述了. 0x01 动态代理 这里先来讲一 ...

  9. TP5自定义路由,为了安全性

    1,入口文件index.php,不要指定任意模块,不然,自定义路由,就不会有任何效果哦 2,在配置文件 route.php  中引进   use think\Router  自带路由文件 3,将路由进 ...

  10. MeteoInfoLab脚本示例:MERRA HDF数据

    MERRA是NOAA的一种再分析资料,HDF数据遵循COARDS协议,读取比较简单.脚本程序: #Add data file folder = 'D:/Temp/hdf/' fns = 'MERRA3 ...