题目要求

编程实现万年历,要求:

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

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

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

小编推荐一个学C语言/C++的学习裙【  712,284,705】,无论你是大牛还是小白,是想转行还是想入行都可以来了解一起进步一起学习!裙内有开发工具,很多干货和技术资料分享!

细节提示

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

  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;
}

如果你对编程感兴趣,想要深入学习。这里分享素材包及学习资源,还有公开课程哦(包含基础知识和项目实践教程)。

(包含C语言、C++、Windows、Qt、Linux)~不论是小白还是进阶者,在这里都能获得成长。点我进入学习基地

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

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

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

  2. 「视频直播技术详解」系列之七:直播云 SDK 性能测试模型

    ​关于直播的技术文章不少,成体系的不多.我们将用七篇文章,更系统化地介绍当下大热的视频直播各环节的关键技术,帮助视频直播创业者们更全面.深入地了解视频直播技术,更好地技术选型. 本系列文章大纲如下: ...

  3. 《算法详解:C++11语言描述》已出版

    经过漫长的编写.修订和印刷过程,书籍<算法详解:C++11语言描述>终于出版了!目前本书已在各大电商平台上架,搜索书名即可找到对应商品.本书的特色在于: 介绍最新的C++11.C++14和 ...

  4. 【翻译】西川善司「实验做出的游戏图形」「GUILTY GEAR Xrd -SIGN-」中实现的「纯卡通动画的实时3D图形」的秘密,前篇(2)

    Lighting和Shading(2)镜面反射的控制和模拟次级表面散射技术 http://www.4gamer.net/games/216/G021678/20140703095/index_2.ht ...

  5. 【翻译】西川善司的「实验做出的游戏图形」「GUILTY GEAR Xrd -SIGN-」中实现的「纯卡通动画的实时3D图形」的秘密,后篇

    http://www.4gamer.net/games/216/G021678/20140714079/     连载第2回的本回,  Arc System Works开发的格斗游戏「GUILTY G ...

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

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

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

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

  8. Struts2学习第三课 Struts2详解

    接着上次的课程 这次我们看struts.xml 修改如下:这里是加上命名空间,默认的是不加,我们手动加上时就要在访问时加上命名空间. <?xml version="1.0" ...

  9. 详解keil采用C语言模块化编程时全局变量、结构体的定义、声明以及头文件包含的处理方法

    一.关于全局变量的定义.声明.引用: (只要是在.h文件中定义的变量,然后在main.c中包含该.h文件,那么定义的变量就可以在main函数中作为全局变量使用) 方法1: 在某个c文件里定义全局变量后 ...

随机推荐

  1. php 计算2点之间的距离

    //获取该点周围的4个点 $distance = 1;//范围(单位千米) $lat = 113.873643; $lng = 22.573969; define('EARTH_RADIUS', 63 ...

  2. python数据类型之set(集合)

    set集合 关注公众号"轻松学编程"了解更多. 1.概述 set与dict类似,但set是一组key的集合,与dict的区别在于set不存储value. 本质:无序且无重复元素的集 ...

  3. 适合 C++ 新手学习的开源项目——在 GitHub 学编程

    作者:HelloGitHub-小鱼干 俗话说:万事开头难,学习编程也是一样.在 HelloGitHub 的群里,经常遇到有小伙伴询问编程语言如何入门方面的问题,如: 我要学习某一门编程语言,有什么开源 ...

  4. Polyglot Translators: Let's do i18n easier! 一款国际化插件小助手!

    在做国际化文本有关的工作时, 是否厌倦了在不同应用或者网页之间频繁地切换进行中文, 繁体, 英文甚至韩文日文的文本翻译工作? 好吧, 我就是受不了频繁在进行文本字符串的转换, 还得跑到百度翻译上面搜索 ...

  5. .NET Core 跨平台资源监控库及 dotnet tool 小工具

    目录 简介 dotnet tool 体验 CZGL.SystemInfo SystemPlatformInfo ProcessInfo 内存监控 NetworkInfo DiskInfo 简介 CZG ...

  6. 痞子衡嵌入式:RT-UFL - 一个适用全平台i.MXRT的超级下载算法设计

    大家好,我是痞子衡,是正经搞技术的痞子.今天给大家带来的是痞子衡的开源项目 RT-UFL. 痞子衡在近两年多的i.MXRT客户项目支持过程中,遇到的一个相当高频的问题就是制作i.MXRT下载算法.我们 ...

  7. js给多级复杂动态变量赋值

    1 function SetVal(field, val) { 2 var arr = field.split("."); 3 var str = arr[0]; 4 if (wi ...

  8. Spring Security 实战干货:OAuth2授权回调的处理机制

    1. 前言 上一文着重讲了当用户发起第三方授权请求是如何初始化OAuth2AuthorizationRequest授权请求对象以及如何通过过滤器进行转发到第三方的.今天我们接着这个流程往下走,来看看服 ...

  9. 主动关闭 tcp_timewait_state_process 处理

    正常情况下主动关闭连接的一端在连接正常终止后,会进入TIME_WAIT状态,存在这个状态有以下两个原因(参考<Unix网络编程>):      1.保证TCP连接关闭的可靠性.如果最终发送 ...

  10. 主动关闭 time wait结构体

    /* * This is a TIME_WAIT sock. It works around the memory consumption * problems of sockets in such ...