## 1.设计目的
通过C语言以面向过程的思想实现一个万年历,模仿生活中的挂历,通过控制台打印的形式输出结果。
本程序暂且命名为:**粗糙牌日历**。
### 1.1基本功能
以月为单位展示日历。比如,通过输入指定年月来查看。
### 1.2拓展功能
制作一个开关机动画
### 1.3技术介绍
#### 1.3.1使用到的库
+ stdio.h
+ time.h
+ windows.h
+ stdlib.h
+ string.h
#### 1.3.2命令行解析
考虑到使用`which`结构做功能选择对于用户来说操作过于繁琐,并不友好。所以这里使用一个命令行的解析函数,解析用户输入的字符串,由程序背后来处理,隐藏细节,使其对用户更友好。
#### 1.3.3头文件共用
为了提高代码的可读性,实现功能模块的更好分离,避免代码全在一个`main.c`文件里,采用引入统一的一个头文件`calender.h`,这样只需要在每个文件里引入`calender.h`就够了。并且这样做了以后,就可以实现对个源文件共同编译。
#### 1.3.4数据储存
因为一个月的天数是不确定,所以在储存一个月的日期的信息时采用了动态分配的方法。定义一个叫`days`的结构体类型,根据月份动态分配一个顺序表。
## 2.需求说明
+ 美观的页面输出
+ 便捷的操作方式
+ 按月份和年份输出
+ 明显的程序加载过程
+ 判断闰年
+ 得到每个月份的正确天数
主要通过一个命令行工具读取用户的输入命令,解析命令,依据解析结果来调用子模块实现万年历跳转到指定日期。
因为每个月的日期不一样,所以通过顺序表来动态地存放一个月的日期数据。
## 3.总体设计
**总体的模块划分**
![](https://www.writebug.com/myres/static/uploads/2021/10/28/96f184a706c48e6244746f8db0ffe982.writebug)
## 4.详细设计
**简单的流程**
![](https://www.writebug.com/myres/static/uploads/2021/10/28/5fe3f17eb3fd7068856448a4fc772ba4.writebug)
### 4.1文件结构
主要将分为6个文件:
+ `main.c` 主函数
+ `calendar.h` 头文件,其他`*.c`文件引入此头文件便可以被链接在一起。<br>主要引入一些必要的头文件,定义了`bool`类型,定义结构体`month`、`days`,声明函数原型。
+ `command.c` 主要就是一个`int *command (char str[])`
+ `date.c` 实现了日期相关的几个函数。
+ `bool is_leap_year( int year )`
+ `int week_day_name( int year, int month , int month_code[] )`
+ `int get_month_days (int year, int month )`
+ `output.c` 与输出相关的一系列函数
+ `void bootstrap()`
+ `void display(char *commod_in, month *specify_month)`
+ `void show_month( month *specify_month )`
+ `void refresh( int time )`
+ `void print_header()`
+ `void print_nav(int year, int month)`
+ `void print_footer()`
+ `void see_you()`
+ `char *current_time()`
+ `sq_list.c` 定义了顺序表的两个相关操作
+ `bool bing_data(month *specify_month )`
+ `bool init_list( month *specify_month, int year, int month )`
具体函数功能参见源码注释
### 4.2具体实现
#### 4.2.1单月输出
如果想要输出一个月的日历的话,只要根据一个月的第一天的具体信息(这一天是星期几)就可以推导出整个月的日历信息。而具体的原理附在 参考 里。计算得到年代码加月份码再加上一个 1 ,得到的结果就是一个月的第一天是星期几。要注意的是月份会因为闰年而改变,所以需要进行一个修正。
得到了一个月的第一天的具体信息,就可进行数据绑定了。天数自增,星期几就等于天数加第一天是星期几的和取余。这里得到的结果星期天的指代码是 0 。
这样就得到了一个月所有信息,再循环遍历输出就可以了。
#### 4.2.2解析字符串
命令行工具需要解析输入的字符串,而C语言本身对字符串的支持本身不是很好,这里只对输入字符串做了一个截取的。再把截取出来的字符串通过`atoi()`函数转化为整形。
#### 4.2.3动态存储实现
使用动态存储的主要原因是:每个月份的天数是不一样的。为了实现结构体定义了如下的结构体。
##### 定义结构体
```c
typedef struct {
int today;
int week_day;
} days ;
typedef struct {
int day_count ;
int week_name;
days *day_base;
} month ;
```
其中`*day_base`是一个以`days`为基本单位的顺序表的头指针。每次需要得到一个月的日期信息时,就将之前的`specify_month`变量里面`day_base`的储存空间释放掉,再重新分配长度为`day_count`的空间。
## 5.设计总结
### 5.1extren 和 static
+ 不可同时对一个变量使用这两个声明。
+ static 声明在`*.h`文件里会产生一种“欺骗”。<br>如果两个文件都 include 这个头文件,并使用的这个静态变量,那么这个静态变量将在两个`*.c`文件的作用域里被重新复制一遍。<br>**static不可实现跨文件传递**
+ 一般 extren 关键字申明在`*.h`里方便进行跨文件使用。
### 5.2结构与指针
`struct_dome.data`与`(*strcut_dome).data`的意义完全不同。
### 5.3输入字符串
char数组应该比你预计的大一个字节。因为char数组最后一个必须是`\0`,作为char数组的结束标志符。
### 5.4调试
调试的时候要断点一点一点逼近出BUG的地方。
注意观看变量的变化。
### 5.5BUG
代码多了以后,BUG 就有可能来自由之前写的代码是把某些变量写死了用来调试,而调试后却没有改正过来。这种 BUG 贼难找。
### 5.6吐槽
C语言的字符串支持真的不好,字符串操作好难受。
不懂指针看到就心累,但真的用起来很神奇。
### 5.7收获
+ 进一步熟悉指针,结构体
+ 运用了分离的思想完成了一个简单的程序
+ 感觉自己独立走完一个程序开发过程成长还是有的