1.1需求分析 在对学生成绩管理系统进行需求分析的过程中,需要确定系统的主要功能,对软件开发的主要目的、软件的使用领域和有关该软件开发的软硬件环境进行详细的分析。 1.1.1 系统概述 学生成绩管理系统主要用于对学生的学号、姓名及各项学科成绩进行增、删、改、查等操作。系统给用户提供了一个简单的人机界面,使用户可以根据提示输入操作项,调用系统提供的管理功能。 1.1.2 系统运行环境 一、硬件环境 处理器:Intel Pentium 166 MX或更高 内存:32MB 硬盘空间:1GB 显卡:SVGA显示适配器 二、软件环境 操作系统:Windows 98/ME/2000/XP 2.1.3 功能需求描述 . 输入学生信息:用户根据提示输入学生的学号、姓名、各科成绩,并由系统计算总分和平均分,并设置名次的初始值为0。可一次性输入多条学生记录。 . 查找学生信息:按照学生姓名查找学生信息,如果存在,则提示用户找到并输出查找结果。 3. 插入学生信息:插入在指定学号的记录前,先查找指定学号是否存在,若存在,直接插入在此记录前,若不存在,则插入在所有记录最后。 . 修改学生信息:提示用户输入要修改的学生学号,查找该学号是否存在,成功则允许修改该学生的姓名、学科成绩等主要内容,并重新计算总分和平均分;失败,显示没找到。 . 删除学生信息:提示用户输入要删除的学生学号,如在,则删除,若没有,则提示相应信息。 6. 保存信息至文件: 将所有的学生成绩信息保存到磁盘文件中,以方便用户管理。 . 读文件:将磁盘文件中的数据读到内存以供用户进行查询、修改、删除、添加、排序等操作。 8. 学生成绩排序:根据学生成绩的总分进行降序排列,并将排序结果显示给用户。 . 计算学生总分和平均分:计算每位学生的总分和平均分,并计算所有学生的总分和平均分。 10. 文件备份:将磁盘文件做一个备份文件,以防止数据意外丢失。 2.2总体设计 2.2.1设计思路 程序设计一般由两部分组成:算法和数据结构,合理地选择和实现一个数据结构和处理这些数据结构具有同样的重要性。在管理程序中,若使用静态数组保存数据,则会占用连续的存储空间。它的缺点是需要预先估计记录大小,如果估计得过大,则浪费空间,如果小了,不容易扩充。特别是当需要动态变化时,例如插入数据和删除数据等操作需要移动数据,容易出错。所以我们使用单链表结构来管理学生成绩,这样,不用事先估计学生人数,方便随时插入和删除学生记录,且不必移动数据,实现动态管理。代价是牺牲一部分空间用来存放表示结点关系的指针。当然,链表的灵活性也带来了管理的复杂性。 2.2.2系统模块结构图 本程序利用单链表存储结构完成对学生成绩的动态管理,其基本功能模块如图3-1所示。 2.2.3数据结构设计 将一个学生当作一个结点,这个结点的类型为结构体,结构体中的域表示学生的属性,每个结点除了存放属性外,还存放结点之间的关系,即存放指向后继结点的指针。结点结构如下: #define N 3 //定义课程门数,可以根据情况设定 typedef struct student//定义数据结构 no[10]; //学号 char name[20]; //姓名 score[N]; //各门课成绩 float sum; //总分 ave;//平均分 int order;//名次 student *next;//指向后继结点的指针 STU;//结构体类型名 2.2.4功能模块设计 一、main()主函数 图3-2 主控函数执行流程 程序采用模块化设计,主函数是程序的入口,主函数不宜复杂,功能尽量在各模块中实现。main()函数执行流程图如图3-2所示。 首先声明一些必要的变量,然后作一无限循环程序,循环体为一个开关语句,该语句的条件值是通过调用主菜单函数得到的返回值,根据该值,调用相应的各功能函数,同时设置一个断点,即当返回值为一定条件时运行exit()函数结束程序,以免造成死循环。 二、menu_select()主菜单 直接利用输出函数printf输出字符串,在屏幕上显示一个菜单,并显示一个提示输入选项,输入0-12之间的数字,将此数字作为菜单函数的返回值返回主函数,主函数根据这个数字调用相应的功能函数。制作简便,操作简单,界面如图3-3所示。 三、输入记录模块 .从键盘输入 输入学生信息的同时,创建链表。按照提示信息输入学号、姓名、三门课程成绩,每输入一个数就按一下回车键,当输入学号首字符为@时结束输入,返回主函数,单链表创建完毕。 在生成链表时,每次新输入的结点放在表头,这样最先输入的结点存放在最后。 对于数据库管理,为避免数据出错或减少数据出错的概率,应考虑数据完整性的验证。该功能由两个函数create()和inputs()完成。设置头指针为空,申请内存空间,如果申请不到,则内存空间满,无法保存数据,则返回主程序;否则输入数据,并进行相应的校验(学号和姓名调用函数inputs输入和校验),成绩就在create()中边输入边验证,以保证输入合法数据。当成绩输入后,计算该生的总分和平均分,并将名次数据先置0,待排序后再赋予新值。数据输入后,将其后继结点指针指向当前头结点,新头指针指向新输入结点,这样新插入结点总在头。数据输入结束后返回链表的头指针到主函数。 .从文件读入 按照文件的读写要求,先定义一个指向文件的指针,输入读入数据的磁盘文件名,然后确定文件的打开方式。如果文件打不开,则退出函数,否则选择一种读文件方式,从文件头开始,将记录读入内存,直到文件尾。文件打开方式和读入方式的确定要依据输出文件的打开方式和写入方式,以免数据读入错误。如果输出文件是二进制文件,块写操作,读入也应设置为二进制打开方式,块读取方式。每读入一条记录,都要做好指针链接关系,本模块将新结点链接到当前链表的尾部,链表的顺序和文件保存的顺序一致。 四、更新记录模块 .查询记录 按照姓名查找结点,从头结点开始顺序查找,成功显示记录信息,失败,显示没找到。姓名是字符串,比较功能利用字符串比较函数strcmp()实现。 .修改记录 输入要进行修改的学生学号,从头结点开始顺序查找该学号是否存在,成功则允许修改该学生的姓名、学科成绩等主要内容,并重新计算总分和平均分;失败,显示没找到。 .删除记录 删除指定学号的学生记录。首先输入要删除结点的学号,输入后根据学号顺序查找结点,如果没找到,则输出没找到信息;;否则,显示找到的结点信息,按任意键后显示已删除信息。注意删除结点时的操作,如果该结点是首结点,则要修改头指针,如图3-4所示;否则,将该结点的前趋指针的后继指向其后继结点,如图3-5所示,然后释放该结点。 4.插入记录 插入结点需要输入插入位置和新结点信息。输入某个结点的学号,新结点将插入在这个指定结点之前。申请空间得到指针info,输入新结点信息,存放到新申请的空间info中。设链表头指针为h,p为指定结点的指针,q为p的前趋指针。从头结点开始循环移动指针p查找指定结点,查找和插入时分以下几种情况处理: ⑴ 指针p为空,如果p等于头指针h,说明链表为空,则新结点即为头结点,修改指针h=info。否则,说明表中没有指定结点,则新结点插入在表尾部,此时q所指结点是最后一个结点,所以修改指针q-next=info,如图3-6所示。 ⑵ 指针p不为空,如果p等于h,说明新结点插入在当前第一个结点之前,为新的头结点,修改指针info-next=p,h=info,如图3-7所示。否则,说明新结点的位置应在p和q两个结点之间,修改指针 五、统计纪录模块 .学生成绩计算 从头指针开始,每读一条记录,将该生的总分累加,并统计记录条数,当所有数据处理完毕,求出平均分,最后输出结果为所有学生的总分和平均分。 .学生成绩排序 对于学生成绩管理,一个很重要的运算是将学生按照分数由高到低排名,本模块实现按照总分排序功能。 排序算法有很多,对于确定的数据结构,应选用一种合适的算法。由于学生信息采用的是单链表存储结构,所以选用直接插入算法较为简单。直接插入算法的基本方法是:每步将一个待排序的记录按其排序码值的大小插到前面已经排序好的表中,直到全部插入为止。基于这样的方法首先将链表的头结点看作是已排好序的结点,然后取下一个结点作为待排序的结点,插入到已排好序的表中。由于单链表的特性,所以具体的思路如下: ⑴ 先将原表头结点作为新排好序表的头结点h,原表下一个结点作为原表头结点h1,设原表如图3-9所示,表中只列出总分数据。 ⑵ 原表头结点为待排序结点,将其总分与新表结点的总分进行比较,如果待排序结点总分大,则插在新表的头,否则插入在其后,原表头结点后移一位,如图3-10所示。 ⑶ 重复第二步,则将原表头结点的总分和新表结点的总分进行比较,如果待排序结点总分小,则移动新表指针,直到找到合适的位置将其插入,直到原表为空,所有结点排序完毕,如图3-11所示。 六、输出记录模块 .文件备份模块 为了保存数据,防止意外发生,为数据做备份是很有必要的。 本模块是将文件读写功能结合到一起的应用。先输入源文件名,再输入目标文件名,然后利用文件读写函数将源文件中的信息写到目标文件中。 .学生信息保存至文件 将学生信息保存到指定文件中。按照文件的读写要求,先定义一个指向文件的指针,输入要保存的磁盘文件名,如果输入的是绝对路径,则文件保存到指定位置;如果只给文件名,则文件保存在VC6.0默认的路径下。然后确定文件的打开方式,打开文件。如果文件打不开,则退出程序,否则选择一种写文件方式,从链表的头指针开始,顺序将记录写入文件,直到所有记录写完,标志就是移动后指针为空。 .显示所有学生信息---输出至屏幕 学生成绩表建立好后,更频繁的操作是显示和查找记录,本模块实现显示所有链表数据功能。输出界面如图3-12所示。 由于单链表只能采取顺序访问的方法,所以定义一个指向结点的临时变量p,初值为单链表的头指针,输出指针所指记录的数据后,将指针后移一个记录,直到p指针值为空,则所有记录输出完毕。设计本模块时注意输出格式以二维表格的形式输出,直观漂亮。输出的第一列为记录号。注意调整格式,对齐表格线。 .3详细设计 .3.1程序预处理 包括加载头文件,定义结构体、常量和变量,并对它们进行初始化。 #include stdio.h //I/O函数 #include stdlib.h //标准库函数 #include string.h //字符串函数 #include ctype.h //字符操作函数 #include conio.h//控制台输入输出函数 #include malloc.h //动态地址分配函数 #include memory.h //内存操作函数 #define N 3 //定义课程门数,可以根据情况设定 typedef struct student//定义数据结构 no[10]; //学号 char name[20]; //姓名 score[N]; //各门课成绩 float sum; //总分 ave;//平均分 int order;//名次 student *next;//指向后继结点的指针 STU;//结构体类型名 /**********以下是函数原型***********/ STU *init(); //初始化函数 STU *create(); //创建链表 print(STU *h); //显示所有记录 void search(STU *h);//查找记录 STU *insert(STU *h); //插入记录 STU *modify(STU *h); //修改记录 *delete(STU *h); //删除记录 void save(STU *h); //记录保存为文件 STU *load(); //从文件中读记录 void compute(STU *h); //计算总分和平均分 STU *sort(STU *h);//排序 void copy();//文件备份 inputs(char *prompt,char *s,int count); //输入字符串,并进行长度验证 int menu_select(); //主菜单函数 .3.2主函数main() 实现对整个程序的运行控制,以及相关功能模块的调用。 int main() *head; //链表定义头指针 =init();//初始化链表 (cls); //清屏 for (;;) //无限循环 (menu_select()) 0:head=init();break; //执行初始化 1:head=create();break; //输入记录创建链表 case 2:print(head);break; //显示所有记录 3:search(head);break;//查找记录 4:head=insert(head);break;//插入记录 case 5:head=modify(head);break;//修改记录 case 6: head=delete (head);break;//删除记录 case 7:save(head);break;//保存文件 case 8:head=load();break;//读文件 9:compute(head);break;//计算总分和平均分 10:head=sort(head);break;//排序 return 0; 11:copy();break;//文件备份 case 12:exit(0);//结束程序 .3.3menu_select()主菜单 用户进入通讯录管理系统时,需要显示主菜单,提示用户进行选择,完成相应任务。此代码被main()函数调用。 /******************************************** 函数功能:显示主菜单,进行功能选择 入口参数:无 出口:返回用户的选项 调用方式:menu_select() ********************************************/ int menu_select() s[80]; int c; (按任意键继续); getch(); (cls);//清屏 (*************学生成绩管理系统菜单***************); printf( 0.初始化); ( 1.输入记录); ( 2.显示全部记录); printf( 3.查找记录); printf( 4.插入记录); printf( 5.修改记录); printf( 6.删除记录); printf( 7.保存文件); printf( 8.读文件); ( 9.计算总分和平均分); printf( 10.按总分排序); printf( 11.文件备份); ( 12.退出); (*****************************************); do (输入你的选择(0-12):); scanf(%s,s); =atoi(s);//将数字字符串转换为数值 while (c<0||c12); return c; .3.4初始化 /******************************************** 函数功能:初始化链表 入口参数:无 出口:返回一个链表空指针 调用方式: init() ********************************************/ STU *init() NULL; .3.5输入记录模块 一、从键盘输入 按照提示信息输入学号、姓名、三门课程成绩,并计算出总分和平均分,同时,创建链表。 /******************************************************* 函数功能:输入字符串,并进行长度验证 入口参数:prompt:提示信息,s:待验证字符串,count:长度 出口:无 调用方式: inputs(“输入姓名:”,info-name,20); *******************************************************/ void inputs(char *prompt,char *s,int count) p[255];; do (prompt);//显示提示信息 scanf(“%s”,p);;//输入字符串 (strlen(p)count) printf(“long!”);//进行长度验证,超过count值重新输入 while(strlen(p)count);; (s,p);//将输入的字符串拷贝到字符串s中 /********************** 函数功能:创建链表 入口参数:无 出口:返回链表头指针 调用方式:create(); **********************/ STU *create() i; float s; *h=NULL,*info; (STU *)malloc(sizeof(STU));//申请空间 (!info)//若指针info为空 (“out of memory”);//提示内存溢出 return NULL; //返回空指针 (“输入学号:”,info-no,10);//输入学号并检验 (info-no[0]==’@’) break;//如果学号首字符为@,结束输入 inputs(“输入姓名:”,info-name,20); //输入姓名并检验 printf(“请输入%d门课程成绩”,N);//提示开始输入成绩 s=0;//计算每个学生的总分,初值为0 for(i=0;i<N;i ) do (“课程%d:”,i 1);//提示输入第几门课程 scanf(“%f”,&info-score[i]);//输入成绩 if(info-score[i]100||info-score[i]<0) printf(“成绩超出指定范围!”); while(info-score[i]100||info-score[i]<0); s =info-score[i];//累加各门成绩 sum=s;//将总分保存 info-ave=s/N;//求平均分 info-order=0;;//未排序前此值为0 info-next=h;//将头结点作为新输入结点的后继结点 h=info;//新输入结点为新的头结点 h;//返回头指针 二、从文件读入数据 /******************************************************* 函数功能:从文件读数据,并把读入的数据链入链表 入口参数:无 出口:返回链表头指针 调用方式:load(); *******************************************************/ STU *load() *p,*q,*h=NULL; FILE *fp; char infile[50]; (请输入文件名,例如:c:f1 e.dat:);//提示文件名格式信息 scanf(%s,infile); ((fp=fopen(infile,rb))==NULL) (读文件); =(STU *)malloc(sizeof(STU)); if(!p) h=p; (!feof(fp)) (1!=fread(p,sizeof(STU),1,fp)); (不能打开文件!); exit(1); (内存溢出!); return h; (内存溢出!); h; q=p; p=p-next; next=NULL;; fclose(fp); (成功读取文件!); return h; .3.6更新记录模块 一、查找记录 从头结点开始顺序查找,查找指定姓名的记录。 /****************************** 函数功能:按照姓名查找记录 入口参数:h:链表头指针 出口:无 调用方式:search(head); ******************************/ void search(STU *h) *p;//移动指针 s[20];//存放姓名的字符数组 system(cls);//清屏 (请输入要查找的姓名:); scanf(%s,s);//输入姓名 =h; (strcmp(p-name,s)&&p!=NULL) =p-next ;//移动指针,指向下一结点 (p==NULL) (没有姓名为%s的学生!,s); (**********************查找结果******************); (学号姓名课程1课程2课程3总分平均分名次); (--------------------------------------------------------------------------); printf(%s%s%.1f%.1f%.1f%.1f%.1f%d,p-no, name,p-score[0],p-score[1],p-score[2],p-sum,p-ave,p-order); (--------------------------------------------------------------------------); 二、插入记录 在指定学号的学生前插入一条记录。若指定学号不存在且学生表为空表,则直接插入此学生信息为头结点,若学生表不为空表,则在表尾插入。若指定学号存在,且为头结点,则新输入的学生信息作为新的头结点插入在指定学号前,否则,作为中间结点直接插入到指定学号前。 /****************************** 函数功能:插入记录 入口参数:h:链表头指针 出口:返回链表头指针 调用方式:insert(head); ******************************/ STU *insert(STU *h) *p,*q,*info;//p指向插入位置,q是其前趋,info指新插入记录 char s[10];//保存插入点位置的学号 float s1;int i;(在哪个学号前插入?);; scanf(%s,s);;//输入插入点学号 printf(请输入新的记录:); =(STU *)malloc(sizeof(STU));//申请空间 if(!info) (out of memory);//提示内存溢出 NULL; //返回空指针 (输入学号:,info-no,10);//输入学号并检验 inputs(输入姓名:,info-name,20); //输入姓名并检验=0;//计算每个学生的总分,初值为0 for(i=0;i<N;i ) (课程%d:,i 1);//提示输入第几门课程 scanf(%f,&info-score[i]);//输入成绩 if(info-score[i]100||info-score[i]<0) printf(成绩超出指定范围!); while(info-score[i]100||info-score[i]<0); s1 =info-score[i];//累加各门成绩 sum=s1;//将总分保存 info-ave=s1/N;//求平均分 info-order=0;//名次赋为0 next=NULL;//设后继指针为空 p=h; q=h; (p!=NULL&&strcmp(p-no,s)) //查找插入位置 (p==NULL) //如果p指针为空,说明没有指定结点 else (p==h) else =p; p=p-next; (p==h)//同时p等于h,说明链表为空 else next=info;//p不为空,但p不等于h,将新结点插在表尾 h=info;//新记录则为头结点 next=p;//如果p等于h,则新结点插入在第一个结点之前 h=info;//新结点为头结点 next=info;//新结点作为q的后继结点 (成功插入!); return h; 三、修改记录 /******************************** 函数功能:修改指定学号的学生信息 入口参数:h:链表头指针 出口:返回链表头指针 调用方式:modify(head); ********************************/ STU *modify(STU *h) *p;//p为查找到要修改的结点指针 char s[10];//存放学号 int i; float s1; system(cls);//清屏 (请输入要修改的学生学号:); scanf(%s,s); p=h; (p!=NULL&&strcmp(p-no,s)) //查找 =p-next; (p==NULL) (找到该学生!学生信息见下列); (学号姓名课程1课程2课程3总分平均分名次); printf(--------------------------------------------------------------------------); printf(%s%s%.1f%.1f%.1f%.1f%.1f%d,p-no,p-name, score[0],p-score[1],p-score[2],p-sum,p-ave,p-order); (--------------------------------------------------------------------------); (没有该学生!); printf(请输入修改信息:); (输入学号:,p-no,10);//输入学号并检验 inputs(输入姓名:,p-name,20); //输入姓名并检验 printf(请输入%d门课程成绩,N);//提示开始输入成绩 =0;//计算每个学生的总分,初值为0 for(i=0;i<N;i ) (课程%d:,i 1);//提示输入第几门课程 scanf(%f,&p-score[i]);//输入成绩 if(p-score[i]100||p-score[i]<0) printf(成绩超出指定范围!); while(p-score[i]100||p-score[i]<0); =p-score[i];//累加各门成绩 return h; sum=s1;//将总分保存 p-ave=s1/N;//求平均分 printf(修改成功!); 四、删除记录 /******************************** 函数功能:删除指定学号的记录 入口参数:h:链表头指针 出口:返回链表头指针 调用方式:delet(head); ********************************/ STU *delet(STU *h) *p,*q;//p为查找到要删除的结点指针,q为其前趋指针 char s[10];//存放学号 char ch; (cls);//清屏 (请输入要删除的学生学号:); scanf(%s,s); =p=h; (p!=NULL&&strcmp(p-no,s)) //查找 =p; p=p-next; (p==NULL) (找到该学生!学生信息见下列); (学号姓名课程1课程2课程3总分平均分名次); printf(--------------------------------------------------------------------------); printf(%s%s%.1f%.1f%.1f%.1f%.1f%d,p-no,p-name, score[0],p-score[1],p-score[2],p-sum,p-ave,p-order); (--------------------------------------------------------------------------); printf(确定要删除这条记录吗?(Y/N):); return h; (没有该学生!); =getch();//此处一定要用getch函数输入,其它函数均不可以 if(ch=='Y' || ch=='y') (p==h)//若被删结点是头结点 else next=p-next;//不是头结点,将p的后继结点作为q的后继结点 h=p-next;//修改头指针指向下一条记录 (p); (删除成功!); .3.7统计记录模块 一、计算总分和平均分 /******************************** 函数功能:计算总分和平均分 入口参数:h:链表头指针 出口:无 调用方式:compute(head); ********************************/ void compute(STU *h) *p;//定义移动指针 i=0;//保存记录条数,初值为0 float s=0;//总分初值为0 float ave=0;//平均分初值为0 p=h;//从头指针开始 while(p!=NULL) ave=s/i; (所有学生的总分为:%.2f,平均分为:%.2f,s,ave); =p-sum;//累加总分 i ; p=p-next; 二、按总分排序 直接插入排序。 /******************************** 函数功能:按总分排序 入口参数:h:链表头指针 出口:返回链表头指针 调用方式:sort(head); ********************************/ STU *sort(STU *h) i=0;//保存名次 *p,*q,*t,*h1;//定义临时指针 =h-next;//将原表头指针所指的下一个结点作为头指针 h-next=NULL;//第一个结点为新表的头结点 while(h1!=NULL)//当原表不为空时,进行排序 =h1;//取原表的头结点 =h1-next;//原表头结点指针后移 p=h;//设定移动指针p,从头指针开始 =h;//设定移动指针q作为p的前趋,初值为头指针 while(p!=NULL&&t-sum<p-sum)//进行总分比较 (p==q)//p==q,说明待排序点值大,应排在首位 //待排序点应插入在中间某个位置q和p之间,如果p为空则是尾部 next=p;//待排序点的后继为p q-next=t;//q的后继是t t-next=p;//待排序点的后继为p h=t;//新头结点为待排序点 =p;//待排序点值小,则新表指针后移 p=p-next; =h;//已排序好的头指针赋给p,准备填写名次 (p!=NULL) (排序成功!); return h; ;//结点序号 p-order=i;//将名次赋值 p=p-next;//指针后移 .3.8输出记录模块 一、输出记录至屏幕 显示所有的学生信息。 /******************************** 函数功能:输出全部记录 入口参数:h:链表头指针 出口:无 调用方式:print(head); ********************************/ void print(STU *h) i=0;//统计记录个数 STU *p;//移动指针 system(cls);;//清屏 p=h; (****************学生信息****************************); printf(序号学号姓名课程1课程2课程3总分平均分名次); printf(-------------------------------------------------------------------------------); while(p!=NULL) ; (%d%s%s%.1f%.1f%.1f%.1f%.1f%d,i,p-no, name,p-score[0],p-score[1],p-score[2],p-sum,p-ave,p-order); (-------------------------------------------------------------------------------); =p-next;; 二、保存数据至文件 /******************************** 函数功能:保存数据到文件 入口参数:h:链表头指针 出口:无 调用方式:save(head); ********************************/ void save(STU *h) *fp;//定义指向文件的指针 STU *p;//定义移动指针 char outfile[50];//文件名 (请输入文件名,例如:c:f1 e.dat:);//提示文件名格式信息 scanf(%s,outfile); ((fp=fopen(outfile,wb))==NULL)
信息发布:广州名易软件有限公司 http://www.myidp.net
|