「實驗課選題詳解」用C語言實現萬年曆

題目要求

編程實現萬年曆,要求:算法

可根據用戶輸入或系統日期進行初始化,若是用戶無輸入則顯示系統日期所在月份的月曆,並突出顯示當前日期;編程

可根據用戶輸入的日期查詢,並顯示查詢結果所在月份的月曆,突出顯示當前日期,並提示是否閏年數組

對任何不合法輸入數據,拒絕查詢並進行提示。dom

小編推薦一個學C語言/C++的學習裙【  712,284,705】,不管你是大牛仍是小白,是想轉行仍是想入行均可以來了解一塊兒進步一塊兒學習!裙內有開發工具,不少乾貨和技術資料分享!函數

 

細節提示

可將思考、編程劃分爲如下幾個模塊:工具

  1. 如何經過已有日期和星期推算要求的日期的星期?
  2. 如何整齊地輸出月曆?
  3. 如何獲取系統時間?
  4. 在有餘力的前提下,如何美化界面?

下面對上面的幾個問題給出粗略的概述。學習

具體實現和技巧性地東西參考後文代碼。開發工具

 

問題1 日期推算

衆所周知,須要推算日期的模擬題都是毒瘤題this

日期推算的算法有不少,這裏只給出個人思路:spa

  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] 中 四、100、400 的倍數的個數就好了。

( 由於我考慮的是 1月1日 ,若是考慮 12月31日 的話,應該變爲 [A+1,B] )

根據容斥原理,記 四、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++WindowsQtLinux)~不管是小白仍是進階者,在這裏都能得到成長。點我進入學習基地

 

相關文章
相關標籤/搜索