《学生信息管理系统》案例设计过程
适用于2022级软工同学
说明:
该文档针对《程序设计实践》课程的大作业-****系统的设计与实现过程中思路的搭建以及出现的问题进行阐述
为了让大家更好地理解整个系统的设计和开发过程,这里拟定有层次地阐述,以“学生成绩管理系统”为例,大家可以根据自己的情况去有选择地学习。
注意:这里也是为啥我不建议有同学自选题目说要做学生信息管理系统的原因,因为希望你能够自己独立地完成一个相对独立的系统;
1 思路整理
基础设计:
问题1:你设计的系统,是否利用“文件”来存储相关数据?
答:若不用,那系统就没有办法将数据存入文件中,只是在内存中,演示的话,只能运行程序,然后一条条地录入数据并演示各种功能,当关闭程序时,你之前录入的数据,都没有了,因为,你没有将相关的数据存入到文件,即磁盘中!我希望你能完成带“文件”存储功能的系统,这样你会对这学期的知识点更加全面地理解!
当然,根据你自己的情况来最好!若你非不用,那就好好地在功能上多考虑一些,也不是不行!
问题2:学生信息管理,你是否打算就是单纯的学生信息管理,不涉及到成绩管理以及课程信息管理等功能呢?还是说都涉及到呢?
答:这里你有几个方案选择:
方案1:就做学生信息管理(比如学号、姓名、性别、籍贯、政治面貌、专业、所属学院等等),用一个文件存储上述信息,比如这个文件名为student即可。
方案2:同方案1类似,在上述信息的基础上,多几个成绩信息,比如(比如学号、姓名、性别、籍贯、政治面貌、专业、所属学院,语文成绩、数学成绩、外语成绩),其实也算是学生信息管理系统,也可以看成学生成绩管理系统,因为也确实是管理里一部分成绩的功能。但其不足之处在于:课程就是语文、数学和外语,当系统做好后,若想自由增加,就很难,必须修改修改很多相应的代码;当然,也是用一个文件(文件名为student)就可以存储上述信息。
其实方案和方案2基本上是一样的,可以视为一回事呀!
方案3:其实这里就是我们在任务书中提到的扩展功能啦,就除了单纯的学生信息管理之外,也扩展了选课功能以及课程信息管理。
这个选择相对功能完善:即学生成绩管理系统,能够管理学生信息+课程信息+课程对应成绩 三方面的数据;其对应的关系呢,图示如下;(这个图呢,实际上,是将来数据库原理课程中的ER实体-关系图)
注意:这里并非让你学习这个,而是让你知道,这个ER图,将来要转换为3个数据库的表,对应于我们现在实现学生选课系统的三个存储数据的文件-学生文件(存储学生信息)、课程文件(存课程的相关信息)、选课文件(存学号、课程号、该课程成绩等信息)
此时:至少需要3个文件(例如student文件、course文件、score文件)来存储上述信息呀!

问题3:系统采用的数据结构是什么?即打算用单链表来存储并操作相关数据?还是利用上学期的结构体数组来操作呢呢?或者是说:有些时候用单链表,到排序的时候,用链表实现起来不爽,再考虑用结构体数组来实现?
答:这里我建议大家可以根据自己的能力进行选择,实际上,用我们这学期的链表最好,这样可以为数据结构课程打下基础,当然,你也可以都用(即有单链表了,也用了一些结构体数组),甚至各实现一个版本,那就更不错,锻炼了自己。
2 初步实现
这里,算是个引入,其实我们在课堂上也这样讲了,但还是担心有的孩子没有听课,连最基本的启动都不会。所以,我先以一个没有涉及文件功能+单链表的一个简单的学生成绩管理系统为例(上课讲过的,教材P283第11-10 例),一步一步带着你尝试完成一个框架!
那有同学会问,那没有文件,后面加文件容易不?当然容易,一步一步来吧啊!
2.1 搭建“学生信息管理系统”的框架
2.1.1 结构体类型的定义
定义学生的结构体类型(根据自己想要保存的学生那些信息来定义结构体中的成员)
你要根据你自己实现系统的功能来定义该结构体呀,当然,你如果采用了方案3
struct stud_node{
int num;
char name[20];
int score;
struct stud_node *next;
};
因为我们希望有一个类似于菜单的界面,这里直接采用例11-10的循环框架,为了让大家学会逐步地扩大自己系统的功能,这里我尽可能地一步一步来阐述。
比如我想实现的系统功能模块包括:(学生信息的录入模块、信息修改模块、信息删除模块、信息浏览模块)即增加,删除,修改以及遍历功能。注意这里并没有涉及 ‘信息查询模块“,将来有赖于大家的进一步实现呀!
你想一下:我们写程序的时候,不可能一口气将所有功能模块(即对应的函数)都实现出来,但是我们可以先进行规划呀!(规划系统初步有哪些功能模块、每个函数的名字、参数等)
2.1.2 功能模块的划分以及定义
当你初步规划功能模块后,通常建议呢,用图的形式展现出来!胜过千言万语!
问:那要是初步规划后,后面还需要加,咋办呢?很简单呀,再加上就好了呀!还得表扬呢!这就叫不断迭代呀!
以例11-1为案例,假如我们当初就想到了学生信息的录入模块、信息修改模块、信息删除模块、信息浏览模块;那就画一个图展示一下呗!

那接下来,对上述功能模块分别给出对应的函数名以及参数定义吧
struct stud_node * Create_Stu_Doc(); /* 新建链表 */
struct stud_node * InsertDoc(struct stud_node * head, struct stud_node *stud); /* 插入 */
struct stud_node * updateDoc(struct stud_node * head, int num); /*修改功能 这个例11-10中没有*/
struct stud_node * DeleteDoc(struct stud_node * head, int num); /* 删除 */
void Print_Stu_Doc(struct stud_node * head); /* 遍历 */
当然,这些名称包括对应参数,都是可以根据功能来改变的!比如说,第三个功能,信息删除功能的第二个参数是num,是表示当你在主函数中输入要删除的学号时,调用该DeleteDoc函数,那我们如果是想这样修改:
情形1:老师,我不想在主函数中录输入要删除的学号,能不能在该函数的肚子里,让用户输入要删号的学号对应的学生,行不行?当然可以了,就把对应的代码移动到该函数的肚子里,当然,对应的函数的参数也需要修改呀,比如改为struct stud_node * DeleteDoc(struct stud_node * head)
情形2:老师,我不想按照学号进行删除,我能否按照学生的姓名进行删除?当然可以了,当然,这样有个缺点就是,学生的名字不能有重复的名字,要不然就得进一步考虑万一有重名的时候咋办?那如何修改函数的参数呢?
这些都是根据你自己定义的工作流程来决定的!
当定义好这些函数的名字和参数之后,那这些函数还没有写出来,咋办?没关系,可以写成空函数,即肚子里啥都不实现,空起来,可以写上一条相应的输出语句,表明这个函数的功能就行,等后期一步一步地实现!
那来吧!
先把主函数写上,观察一下,一个主函数+4个用户自定义函数
注意一下,我把主函数中的一些需要录入输入后才能调用对应函数的代码暂时注释掉,为了更好地演示主函数中菜单调用每个函数的效果!
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct stud_node{
int num;
char name[20];
int score;
struct stud_node *next;
};
struct stud_node * Create_Stu_Doc(); /* 新建链表 */
struct stud_node * InsertDoc(struct stud_node * head, struct stud_node *stud); /* 插入 */
struct stud_node * updateDoc(struct stud_node * head, int num); /* 修改 */
struct stud_node * DeleteDoc(struct stud_node * head, int num); /* 删除 */
void Print_Stu_Doc(struct stud_node * head); /* 遍历 */
int main(void)
{
struct stud_node *head, *p;
int choice, num, score;
char name[20];
int size = sizeof(struct stud_node);
do{
printf("1:Create 2:Insert 3:update 4:delete 5:browse 0:Exit\n");
scanf("%d", &choice);
switch(choice){
case 1:
head = Create_Stu_Doc();
break;
case 2:
/*
printf("Input num,name and score:\n");
scanf("%d%s%d", &num,name, &score);
p = (struct stud_node *) malloc(size);
p->num = num;
strcpy(p->name, name);
p->score = score;
*/
head = InsertDoc(head, p);
break;
case 3:
scanf("%d", &num);
head = updateDoc(head, num);
break;
case 4:
printf("Input num:\n");
scanf("%d", &num);
head = DeleteDoc(head, num);
break;
case 5:
Print_Stu_Doc(head);
break;
case 0:
break;
}
}while(choice != 0);
return 0;
}
先不对4个用户自定义函数进行实现,就用一个输出语句来代替吧!
/*新建链表*/
struct stud_node * Create_Stu_Doc()
{
printf("你好,该模块完成新建链表的功能\n");
}
struct stud_node * InsertDoc(struct stud_node * head, struct stud_node *stud)
{
printf("你好,该模块完成在链表中插入一个节点的功能\n");
/* */
}
struct stud_node * updateDoc(struct stud_node * head, int num)
{
printf("你好,该模块完成修改功能\n");
/* 参数1:给我一个链表的头指针,参数2:给我一个学号,当然是由其他函数传入给我 */
}
struct stud_node * DeleteDoc(struct stud_node * head, int num)
{
printf("你好,该模块完成删除链表的一个节点的功能\n");
/* 参数1:给我一个链表的头指针,参数2:给我一个要删除学生的学号,当然是由其他函数传入给我 */
}
void Print_Stu_Doc(struct stud_node * head)
{
printf("你好,该模块完成浏览即遍历整个链表中所有数据的功能\n");
/* 参数1:给我一个链表的头指针即可*/
}
那将上述两部分代码放在一个文件中,那岂不是可以运行了呢?来试试吧!

问:有同学问,这样的菜单也不好看呀,能不能变个样子呀!当然可以了
尝试写一个menu函数,就单干输出菜单行不行?让主函数开始调用它,看看行不行呗!
void menu()
{ printf("\n\n***************************\n");
printf("\t1:新建链表\n\n\t2:插入一个节点\n\n\t3:修改一个结点\n\n\t4:删除一个结点\n\n\t5:浏览所有信息\n\n\t0:退出系统\n");
printf("***************************\n");
}
那就修改一下主函数吧!如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct stud_node{
int num;
char name[20];
int score;
struct stud_node *next;
};
void menu();/* 显示菜单 可别忘记这里对菜单函数进行声明呀,具体menu函数实现,放到最后一个函数后面*/
struct stud_node * Create_Stu_Doc(); /* 新建链表 */
struct stud_node * InsertDoc(struct stud_node * head, struct stud_node *stud); /* 插入 */
struct stud_node * updateDoc(struct stud_node * head, int num); /* 修改 */
struct stud_node * DeleteDoc(struct stud_node * head, int num); /* 删除 */
void Print_Stu_Doc(struct stud_node * head); /* 遍历 */
int main(void)
{
struct stud_node *head, *p;
int choice, num, score;
char name[20];
int size = sizeof(struct stud_node);
do{
menu();/* 这个地方就是调用菜单menu函数!*/
scanf("%d", &choice);
switch(choice){
case 1:
head = Create_Stu_Doc();
break;
case 2:
/*
printf("Input num,name and score:\n");
scanf("%d%s%d", &num,name, &score);
p = (struct stud_node *) malloc(size);
p->num = num;
strcpy(p->name, name);
p->score = score;
*/
head = InsertDoc(head, p);
break;
case 3:
scanf("%d", &num);
head = updateDoc(head, num);
break;
case 4:
printf("Input num:\n");
scanf("%d", &num);
head = DeleteDoc(head, num);
break;
case 5:
Print_Stu_Doc(head);
break;
case 0:
break;
}
}while(choice != 0);
return 0;
}

问:有同学说,还不够好看呀,不能显示跟系统有关的内容呀?
答:那就直接修改menu函数即可,其他的不用动即可!
继续修改menu函数
void menu()
{ printf("\n\n********青果学生信息管理系统*************\n\n");
printf("\t1:新建一个学生信息链表\n\n\t2:插入一个学生信息\n\n\t3:修改一个学生信息\n\n\t4:删除一个学生信息\n\n\t5:浏览所有学生信息\n\n\t0:退出学生信息系统\n");
printf("**************************************\n");
}

是不是感觉好多了?嗯!
问:那功能没有实现呀?
答:不要急,接下来就开始啦!将每一个函数的实现过程替换掉对应函数肚子的那个输出语句!
第一:以新建链表的函数为例Create_Stu_Doc,其内部实现,是需要调用节点插入InsertDoc函数,那这两个函数要一起实现,要不然,光一个Create_Stu_Doc可不够呀!
第二,那写的对不对也需要测试,比如说:你创建成功了一个学生信息单链表,那到底创建成功没有?,那就赶紧把遍历即浏览函数Print_Stu_Doc写一下,这样,调用输出一下,不就可以更好地看一下是否创建成功了没有?
第三:当你写好了修改函数,和删除函数,是不是也需要测试一下,也离不开浏览函数Print_Stu_Doc,所以,Print_Stu_Doc必须是正确无误的呀!
来,一步一步啦,不要急
先写Create_Stu_Doc函数,来吧
注意,我们讲课时,都希望你创建的是带头节点的单链表,那下面的代码是创建的是啥链表呢??
你看,它调用的是一个InsertDoc(head, p)函数,那我们看看
/*新建链表*/
struct stud_node * Create_Stu_Doc()
{
struct stud_node * head,*p;
int num,score;
char name[20];
int size = sizeof(struct stud_node);
head = NULL;
printf("Input num,name and score:\n");
scanf("%d%s%d", &num,name, &score);
while(num != 0){
p = (struct stud_node *) malloc(size);
p->num = num;
strcpy(p->name, name);
p->score = score;
head = InsertDoc(head, p); /* 调用插入函数 */
scanf("%d%s%d", &num, name, &score);
}
return head;
}
一起读一下下面的insertDoc函数吧,区别于我们讲过的,你看一下,
该函数两个参数,一个是给我一个链表,第二个参数,就是传给我的一个学生结构体变量的指针
然后呢?这个学生要插入到什么位置呢?下面的代码仔细看呀!
情况1:若给我传过来的head为NULL,即是空链表,新来的这个就是第一个结点(这说明这个算法是否考虑头结点)
情况2: if(ptr->num <= ptr2->num) 就是判断一下传入的这个学生的学号,如果是处于两个学生节点的学号之间,那就插入到这两个学生节点之间; 这点区别于我们之前讲过的头插,尾插,而是按照学号的顺序插入呀,这就是灵活变化呀,根据题目的要求来变化呀!
情况3:就是else的后面的,不是第一个,也不是中间的,那就放到单链表的最后,作为尾节点!
/* 插入操作 */
struct stud_node * InsertDoc(struct stud_node * head, struct stud_node *stud)
{
struct stud_node *ptr ,*ptr1, *ptr2;
ptr2 = head;
ptr = stud; /* ptr指向待插入的新的学生记录结点 */
/* 原链表为空时的插入 */
if(head == NULL){
head = ptr; /* 新插入结点成为头结点 */
head->next = NULL;
}
else{ /* 原链表不为空时的插入 */
while((ptr->num > ptr2->num) && (ptr2->next != NULL)){
ptr1 = ptr2; /* ptr1, ptr2各后移一个结点 */
ptr2 = ptr2->next;
}
if(ptr->num <= ptr2->num){ /* 在ptr1与ptr2之间插入新结点 */
if(head == ptr2){
head = ptr;
}else{
ptr1->next = ptr;
}
ptr->next = ptr2;
}else{ /* 新插入结点成为尾结点 */
ptr2->next = ptr;
ptr->next = NULL;
}
}
return head;
}
不管如何,那为了验证新建链表函数和插入节点函数是否成功,还需要写一下浏览函数,这个函数实现很简单,就是给我一个单链表的头指针,我来“顺藤摸瓜”输出每个学生节点的内容!
/*遍历操作*/
void Print_Stu_Doc(struct stud_node * head)
{
struct stud_node * ptr;
if(head == NULL){
printf("\n对不起,该学生链表中没有数据\n");
return;
}
printf("\nThe Students' Records Are: \n");
printf("Num\t Name\t Score\n");
for(ptr = head; ptr != NULL; ptr = ptr->next){
printf("%d\t%s\t%d \n", ptr->num, ptr->name, ptr->score);
}
}
那这样吧,我们就把主函数也弄过来,把调用Create_Stu_Doc、InsertDoc、 Print_Stu_Doc三个函数的对应的注释去掉吧
这里我给一个相对完整的代码吧!
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct stud_node{
int num;
char name[20];
int score;
struct stud_node *next;
};
void menu();/* 显示菜单*/
struct stud_node * Create_Stu_Doc(); /* 新建链表 */
struct stud_node * InsertDoc(struct stud_node * head, struct stud_node *stud); /* 插入 */
struct stud_node * updateDoc(struct stud_node * head, int num); /* 修改 */
struct stud_node * DeleteDoc(struct stud_node * head, int num); /* 删除 */
void Print_Stu_Doc(struct stud_node * head); /* 遍历 */
int main(void)
{
struct stud_node *head, *p;
int choice, num, score;
char name[20];
int size = sizeof(struct stud_node);
do{
menu();
scanf("%d", &choice);
switch(choice){
case 1:
head = Create_Stu_Doc();
break;
case 2:
printf("Input num,name and score:\n");
scanf("%d%s%d", &num,name, &score);
p = (struct stud_node *) malloc(size);
p->num = num;
strcpy(p->name, name);
p->score = score;
head = InsertDoc(head, p);
break;
case 3:
scanf("%d", &num);
head = updateDoc(head, num);
break;
case 4:
printf("Input num:\n");
scanf("%d", &num);
head = DeleteDoc(head, num);
break;
case 5:
Print_Stu_Doc(head);
break;
case 0:
break;
}
}while(choice != 0);
return 0;
}
/*新建链表*/
struct stud_node * Create_Stu_Doc()
{
struct stud_node * head,*p;
int num,score;
char name[20];
int size = sizeof(struct stud_node);
head = NULL;
printf("Input num,name and score:\n");
scanf("%d%s%d", &num,name, &score);
while(num != 0){
p = (struct stud_node *) malloc(size);
p->num = num;
strcpy(p->name, name);
p->score = score;
head = InsertDoc(head, p); /* 调用插入函数 */
scanf("%d%s%d", &num, name, &score);
}
return head;
}
/* 插入操作 */
struct stud_node * InsertDoc(struct stud_node * head, struct stud_node *stud)
{
struct stud_node *ptr ,*ptr1, *ptr2;
ptr2 = head;
ptr = stud; /* ptr指向待插入的新的学生记录结点 */
/* 原链表为空时的插入 */
if(head == NULL){
head = ptr; /* 新插入结点成为头结点 */
head->next = NULL;
}
else{ /* 原链表不为空时的插入 */
while((ptr->num > ptr2->num) && (ptr2->next != NULL)){
ptr1 = ptr2; /* ptr1, ptr2各后移一个结点 */
ptr2 = ptr2->next;
}
if(ptr->num <= ptr2->num){ /* 在ptr1与ptr2之间插入新结点 */
if(head == ptr2){
head = ptr;
}else{
ptr1->next = ptr;
}
ptr->next = ptr2;
}else{ /* 新插入结点成为尾结点 */
ptr2->next = ptr;
ptr->next = NULL;
}
}
return head;
}
struct stud_node * updateDoc(struct stud_node * head, int num)
{
printf("你好,该模块完成修改功能\n");
/* 参数1:给我一个链表的头指针,参数2:给我一个学号,当然是由其他函数传入给我 */
}
struct stud_node * DeleteDoc(struct stud_node * head, int num)
{
printf("你好,该模块完成删除链表的一个节点的功能\n");
/* 参数1:给我一个链表的头指针,参数2:给我一个要删除学生的学号,当然是由其他函数传入给我 */
}
/*遍历操作*/
void Print_Stu_Doc(struct stud_node * head)
{
struct stud_node * ptr;
if(head == NULL){
printf("\nNo Records\n");
return;
}
printf("\nThe Students' Records Are: \n");
printf("Num\t Name\t Score\n");
for(ptr = head; ptr != NULL; ptr = ptr->next){
printf("%d\t%s\t%d \n", ptr->num, ptr->name, ptr->score);
}
}
void menu()
{ printf("\n\n********青果学生信息管理系统*************\n\n");
printf("\t1:新建一个学生信息链表\n\n\t2:插入一个学生信息\n\n\t3:修改一个学生信息\n\n\t4:删除一个学生信息\n\n\t5:浏览所有学生信息\n\n\t0:退出学生信息系统\n");
printf("**************************************\n");
}

此处,也是一个可以改进的地方呀,难道必须每次都得输入0以后,还得输入一个姓名和分数才行吗?
如何改进呢?你答辩的时候,是不是可以给我炫耀一下,你是如何改进 的呢?也是头脑风暴之一呀!



这里,三个函数都通过测试,那至于删除函数和修改函数,你自行补充进去,是不是就可以了呢?
你来做吧!
这里我假定你已经补充进去了!
至此,你是不是已经对一个简单的学生信息管理系统有了了解呢!
那如何加上“文件”功能?不能每一次运行,都让我输入大量的数据呀!
2.2 在上述“学生信息管理系统”框架基础上加上“文件“的功能(未写完)
思考一下:
文件的作用是什么?在什么时候使用?
答:
场景1:最简单的场景,就是事先已经有一个文件,里面按照格式已经录入了若干条学生的信息数据;在程序运行的时候,先用一个load函数,就是读入该文件,将该文件读入到单链表中或者结构体数组中,那后面的至于插入操作、增加操作、删除操作,修改操作、查询操作等,都在单链表或者结构体数组上完成,等最后退出整个系统的时候,调用一个save函数,即再把单链表或者结构体数组中的数据写回到文件中,就可以了!
场景2:假如是第一次使用该系统,那必然没有该数据文件啦,那程序应该可以提醒创建一个文件,然后,你录入的数据,就可以存入到该文件中
那我们回来看一下我们已经写好的代码里,是如何创建起来链表的呢?
现有的代码的流程是:选择1,调用 head = Create_Stu_Doc();这个函数,
Create_Stu_Doc()函数的具体实现为:
struct stud_node * Create_Stu_Doc()
{
struct stud_node * head,*p;
int num,score;
char name[20];
int size = sizeof(struct stud_node);
head = NULL;
printf("Input num,name and score:\n");
scanf("%d%s%d", &num,name, &score);
while(num != 0){
p = (struct stud_node *) malloc(size);
p->num = num;
strcpy(p->name, name);
p->score = score;
head = InsertDoc(head, p); /* 调用插入函数 */
scanf("%d%s%d", &num, name, &score);
}
return head;
}
观察一下,循环地 scanf("%d%s%d", &num,name, &score); 就是循环地读取数据,那我们能否将这里的数据的读入,改为从文件读入呢?即,读一行数据,创建一个节点,链接进链表,那读取到最后,不就是创建完成了?
扩展功能的头脑风暴(未写完)
你自己实现的任何你自己觉得是头脑风暴中的功能,都可以在答辩的时候给我阐述出来,不要怕小,其实小功能也能体现出不少的细节!
程序整体以及功能方面:
(1)就各个系统而言,例:若只完成图书管理的图书管理系统,也确实能够满足基本要求,若能够进一步完成图书借阅功能,那将是一个不错的功能扩展,比如学生成绩管理系统,加上课程的管理,以及对应成绩的管理,也是功能的扩展。
(2)多文件包含:将写好的一个很大很长的程序,进行拆解,写成多文件包含的形式!虽然是在设计之初就应该将这些搞定,很多同学都是写好程序之后,才想着拆分,其实也是一个好主意!也避免拆坏了!
(3)具体功能实现,能否利用一些非课堂中学习的算法来实现;(比如排序,除了学习过的冒泡和选择排序外,自己能够自学数据结构课程中的其他排序算法,并实现,那也就锻炼了自学能力)
细节:
(1)录入数据的时候,学号是否重复呢?如何处理呢?
(2)录入数据的时候,学号、身份证号等类似的信息,通常有固定的长度,若用户输入错误了,程序应该有类似于校验并提示的功能;
(3)身份证号与出生日期是否一致?这其实也是一个校验的功能;要想实现,当然也需要写一个小的函数呀!
(4)删除数据的时候的提示信息“确定要删除”等人性化的处理细节;
(5)查询功能的处理,可以尝试实现比如模糊查询,组合查询等功能,比如查询一下图书名称含有为“程序设计”字样的图书、组合查询:比如查询一下“清华大学出版社”出版的含有“程序设计”字样的图书等等
(6)各类报表的输出,自己想一些功能,比如导出**个学院的借阅图书前十名的学生,导出到某一个文档中,其实就是一个写入数据到另一个文件的过程;
(7)数据导入功能的设计,其实就是从一个文件中读出,写入到另外一个文件中。
所以,你会发现,所谓的细节,实现起来呢,并不是很难,就是要去想,要站到用户的角度去想,哪些功能和细节是容易忽略的,然后,你将它设计出来。