「实验课选题详解」用C语言实现万年历
题目要求
编程实现万年历,要求:
可根据用户输入或系统日期进行初始化,如果用户无输入则显示系统日期所在月份的月历,并突出显示当前日期;
可根据用户输入的日期查询,并显示查询结果所在月份的月历,突出显示当前日期,并提示是否闰年
对任何不合法输入数据,拒绝查询并进行提示。
小编推荐一个学C语言/C++的学习裙【 712,284,705】,无论你是大牛还是小白,是想转行还是想入行都可以来了解一起进步一起学习!裙内有开发工具,很多干货和技术资料分享!
细节提示
可将思考、编程划分为以下几个模块:
- 如何通过已有日期和星期推算要求的日期的星期?
- 如何整齐地输出月历?
- 如何获取系统时间?
- 在有余力的前提下,如何美化界面?
下面对上面的几个问题给出粗略的概述。
具体实现和技巧性地东西参考后文代码。
问题1 日期推算
众所周知,需要推算日期的模拟题都是毒瘤题
日期推算的算法有很多,这里只给出我的思路:
- 推出差了多少天。
- 用数学公式推出星期。
这条公式是 \((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语言实现万年历的更多相关文章
- 【实验课选题详解】用C语言实现万年历
题目要求 编程实现万年历,要求: 可根据用户输入或系统日期进行初始化,如果用户无输入则显示系统日期所在月份的月历,并突出显示当前日期: 可根据用户输入的日期查询,并显示查询结果所在月份的月历,突出显示 ...
- 「视频直播技术详解」系列之七:直播云 SDK 性能测试模型
关于直播的技术文章不少,成体系的不多.我们将用七篇文章,更系统化地介绍当下大热的视频直播各环节的关键技术,帮助视频直播创业者们更全面.深入地了解视频直播技术,更好地技术选型. 本系列文章大纲如下: ...
- 《算法详解:C++11语言描述》已出版
经过漫长的编写.修订和印刷过程,书籍<算法详解:C++11语言描述>终于出版了!目前本书已在各大电商平台上架,搜索书名即可找到对应商品.本书的特色在于: 介绍最新的C++11.C++14和 ...
- 【翻译】西川善司「实验做出的游戏图形」「GUILTY GEAR Xrd -SIGN-」中实现的「纯卡通动画的实时3D图形」的秘密,前篇(2)
Lighting和Shading(2)镜面反射的控制和模拟次级表面散射技术 http://www.4gamer.net/games/216/G021678/20140703095/index_2.ht ...
- 【翻译】西川善司的「实验做出的游戏图形」「GUILTY GEAR Xrd -SIGN-」中实现的「纯卡通动画的实时3D图形」的秘密,后篇
http://www.4gamer.net/games/216/G021678/20140714079/ 连载第2回的本回, Arc System Works开发的格斗游戏「GUILTY G ...
- 撩课-Mysql详解第3部分sql分类
学习地址:[撩课-JavaWeb系列1之基础语法-前端基础][撩课-JavaWeb系列2之XML][撩课-JavaWeb系列3之MySQL][撩课-JavaWeb系列4之JDBC][撩课-JavaWe ...
- 撩课-MySQL详解1-数据库简介
学习地址:[撩课-JavaWeb系列1之基础语法-前端基础][撩课-JavaWeb系列2之XML][撩课-JavaWeb系列3之MySQL][撩课-JavaWeb系列4之JDBC][撩课-JavaWe ...
- Struts2学习第三课 Struts2详解
接着上次的课程 这次我们看struts.xml 修改如下:这里是加上命名空间,默认的是不加,我们手动加上时就要在访问时加上命名空间. <?xml version="1.0" ...
- 详解keil采用C语言模块化编程时全局变量、结构体的定义、声明以及头文件包含的处理方法
一.关于全局变量的定义.声明.引用: (只要是在.h文件中定义的变量,然后在main.c中包含该.h文件,那么定义的变量就可以在main函数中作为全局变量使用) 方法1: 在某个c文件里定义全局变量后 ...
随机推荐
- java log4j 的一个bug
java项目中使用log4j记录日志几乎成了标配, 最近一个项目中出了个问题 现象是这样的: 不连vpn程序一切正常,连上VPN启动程序 直接异常退出, 错误日志直接指向了 log4j 库 org ...
- Django项目-个人网站之投票模块
Django项目之个人网站 关注公众号"轻松学编程"了解更多. Github地址:https://github.com/liangdongchang/MyWeb.git 感兴趣的可 ...
- 考场(NOIP/ICPC)沙雕错误锦集(大赛前必看,救命提分良药)
记住,无论什么测试,一定要先打三题暴力(至少不会被屠得太惨) 2018.10.4 1.记得算内存.(OI一年一场空,没算内存见祖宗) 2018.10.6 1.在二分许多个字符串时(二分长度),要以长度 ...
- [CF160D]Edges in MST (最小生成树+LCA+差分)
待填坑 Code //CF160D Edges in MST //Apr,4th,2018 //树上差分+LCA+MST #include<cstdio> #include<iost ...
- IOCP 模型2 AcceptEx
// IOCP2.cpp : Defines the entry point for the console application. // #include "stdafx.h" ...
- C#编译时与运行时
曾几何时,对C#编译时与运行时的理解总是不是那么明显.以下对此部分说明一下自己的理解. 定义 编译时 将C#程序编译成中间代码的过程.其过程是对程序进行词法分析,语法分析等. 运行时 就是程序最终分配 ...
- 不停机不更新代码线上调试BUG的工具
如果你有以下痛点,请你查看本文章: 1.我改的代码为什么没有执行到?难道是我没 commit?分支搞错了? 2.遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗? 3.线上遇到某个用户的 ...
- XJOI 夏令营501-511NOIP训练18 高二学堂
在美丽的中山纪念中学中,有座高二学堂,同样也是因为一个人,让它们变 成了现在这个样子~那就是我们伟大的级主任.因为他,我们又迎来了一个木有电影,只有对答案的段考日:又迎来了一个不是大礼拜,而是小礼拜的 ...
- php之策略模式
策略模式:封装算法,选择所用具体实现的置业由客户对象承担. 将算法具体类,然后互相替换,不影响客户. <?php /** * 设计模式之策略模式 * User: 小狗蛋儿 * Date: 201 ...
- Jmeter(二十六) - 从入门到精通 - 搭建开源论坛JForum(详解教程)
1.简介 今天这篇文章主要是给大家讲解一下,如何部署测试环境,这里宏哥部署一个开源测论坛,后边的文章中会用到这个论坛,并且也看到童鞋们在群里讨论如何在开发将测试包发给你以后,你如何快速地部署测试环境. ...