设计一个学生成绩管理系统,包括学生成绩数据的增删改查等基本操作,通过编程实现如下菜单驱动的学生成绩管理系统:
(1)录入每个学生的学号、姓名和各科考试成绩;
(2)计算每门课程的总分和平均分;
(3)计算每个学生的总分和平均分;
(4)按每个学生的总分由高到低排出名次表;
(5)按每个学生的总分由低到高排出名次表;
(6)按学号由小到大排出成绩表;
(7)按姓名的字典顺序排出成绩表;
(8)按学号查询学生排名及其考试成绩;
(9)按姓名查询学生排名及其考试成绩;
(10)按优秀(90-100)、良好(80-89)、中等(70-79)、及格(60-69)、不及格(0-59)5个类别,对每门课程分别统计每个类别的人数以及所占的百分比;
(11)输出每个学生的学号、姓名、各科考试成绩,以及每门课程的总分和平均分;
(12)将每个学生的记录信息写入文件;
(13)从文件中读出每个学生的记录信息并显示。
首先,用户输入菜单选项(自然数0-13),程序会对非法输入进行判断,并提示用户重新输入。另外,当用户输入0-13中的某个数时,未必都是合法的。例如在输入1或13之前输入其他数字,即在录入成绩之前进行其他操作。程序亦会对此类非法输入进行判断并提示重新输入。
程序支持用户先后输入多个菜单选项,程序会在完成当前菜单选项的任务之后再次在屏幕上打印出菜单,以供用户再次选择,直至用户输入0终止程序。
为了节省空间,程序会采用动态分配内存的方式,这也就要求程序必须预先知道所需空间的大小。为了达到这一目的,在录入成绩时,程序会提示用户输入学生人数、课程门数(均为整型)以确定所需空间大小。
进而,在录入成绩的时候会涉及到不同类型的变量(学号(长整型)、姓名(字符串)、成绩(整型))。程序会提供表头,用户只需对应表头输入相应信息即可。在这里,程序默认输入的学生姓名无空格。
除此之外,用户无需输入大量数据,在个别菜单选项中,用户只需根据提示信息输入少量数据即可。若输入数据不合法,程序亦会提示用户重新输入。并且,程序中加入了对文件打开失败、动态内存分配不成功等异常情况的处理,以增强程序的健壮性。
考虑到表格更直观,可读性更好,程序的输出主要以表格的形式。
另外,由于每次录入的学生学号、姓名长度会有所不同,程序中加入了对它们长度的判断,以较为对齐的方式输出表格,让用户阅读时更为舒适且不易出错。
当然,对于一些重要信息(例如学生的排名)程序是换行单独输出的,这样的考虑是为了让用户能更清楚地了解到一些重要信息。
代码下载
#include
#include
#include
#include "windows.h"
int NumOfStudent,NumOfCourse,flag,*Total;
typedef struct student
{
long int num;
char name[100];
int score[10];
int rank;
}STUDENT;
STUDENT *stu;
void Menu(); //函数、变量声明
int Input_Choice();
void Table_Head();
void Data();
int Legal_Input();
void Input_Data();
void Course_Score(STUDENT stu[]);
void Student_Score(STUDENT stu[]);
void DownSort(int *Total);
void UpSort(int *Total);
void ID_Sort(STUDENT stu[]);
void Name_Sort(STUDENT stu[]);
void QuickSort(int a[],STUDENT b[],int low,int high);
void swap(int a[],STUDENT b[],int low,int high);
int partion(int a[],STUDENT b[],int low,int high);
void Search_Number();
void Search_Name();
void Statistic();
void Output_Data();
void wfile();
void rfile();
void Menu() //显示菜单
{
printf("1.Input record\n");
printf("2.Calculate total and average score of every course\n");
printf("3.Calculate total and average score of every student\n");
printf("4.Sort in descending order by total score of every student\n");
printf("5.Sort in ascending order by total score of every student\n");
printf("6.Sort in ascending order by number\n");
printf("7.Sort in dictionary order by name\n");
printf("8.Search by number\n");
printf("9.Search by name\n");
printf("10.Statistic analysis for every course\n");
printf("11.List record\n");
printf("12.Write to a file\n");
printf("13.Read from a file\n");
printf("0.Exit\n");
printf("Please enter your choice:\n");
}
int Input_Choice() //输入菜单选项
{
int choice,check;
B:
check=scanf("%d",&choice);
while(check!=1||choice<0||choice>13)
{
while(getchar()!='\n');
printf("Input Error! Please enter your choice again:\n");
check=scanf("%d",&choice);
}
if(choice==1||choice==13||choice==0) flag=1;
if(!flag)
{
printf("Input Error! Please enter your choice again:\n");
goto B;
}
else return choice;
}
void Table_Head() //输出成绩表表头
{
int i;
printf("IDnumber Name ");
for(i=1;i<=NumOfCourse;i++) printf("Course%d ",i);
printf("\n");
for(i=1;i<=NumOfCourse*9+14;i++) printf("-");
printf("\n");
}
void Data() //输出成绩表主体数据
{
int i,j;
for(i=0;i<NumOfStudent;i++)
{
printf("%8ld %s ",stu[i].num,stu[i].name);
for(j=0;j<NumOfCourse;j++) printf(" %3d ",stu[i].score[j]);
printf("\n");
}
}
int Legal_Input() //合法化输入
{
int n,check;
check=scanf("%d",&n);
while(check!=1||n<0)
{
while(getchar()!='\n');
printf("Input Error! Please enter again:\n");
check=scanf("%d",&n);
}
return n;
}
void Input_Data() //功能1:录入每个学生的学号、姓名和各科考试成绩
{
int i,j;
printf("Please enter the number of students:\n");
NumOfStudent=Legal_Input();
printf("Please enter the number of courses:\n");
NumOfCourse=Legal_Input();
stu=(STUDENT*)calloc(NumOfStudent,sizeof(STUDENT));
Total=(int*)calloc(NumOfStudent,sizeof(int));
if(stu==NULL||Total==NULL)
{
printf("calloc error!\n"); //内存申请不成功的处理
}
Table_Head();
for(i=0;i<NumOfStudent;i++)
{
scanf("%ld",&stu[i].num);
scanf("%s",stu[i].name);
for(j=0;j<NumOfCourse;j++)
{
stu[i].score[j]=Legal_Input();
Total[i]+=stu[i].score[j];
}
}
QuickSort(Total,stu,0,NumOfStudent-1);
for(i=NumOfStudent-1;i>=0;i--) stu[i].rank=NumOfStudent-i;
}
void Course_Score(STUDENT stu[]) //功能2:计算每门课程的总分和平均分
{
long int total=0;
int i,j;
printf(" total average\n");
for(i=0;i<NumOfCourse;i++)
{
total=0;
for(j=0;j<NumOfStudent;j++)
total+=stu[j].score[i];
printf("Course%d: %5ld %.2f\n",i+1,total,(double)total/NumOfStudent);
}
}
void Student_Score(STUDENT stu[]) //功能3:计算每个学生的总分和平均分
{
int i;
printf(" total average\n");
for(i=0;i<NumOfStudent;i++)
{
printf("%s: %5d %.2f\n",stu[i].name,Total[i],(double)Total[i]/NumOfCourse);
}
}
void DownSort(int *Total) //功能4:按每个学生的总分由高到低排出名次表
{
int i,j;
QuickSort(Total,stu,0,NumOfStudent-1);
Table_Head();
for(i=NumOfStudent-1;i>=0;i--)
{
stu[i].rank=NumOfStudent-i;
printf("%8ld %s ",stu[i].num,stu[i].name);
for(j=0;j<NumOfCourse;j++) printf(" %3d ",stu[i].score[j]);
printf("\n");
}
}
void UpSort(int *Total) //功能5:按每个学生的总分由低到高排出名次表
{
QuickSort(Total,stu,0,NumOfStudent-1);
Table_Head();
Data();
}
void ID_Sort(STUDENT stu[]) //功能6:按学号由小到大排出成绩表
{
long int number[100];
int i;
for(i=0;i<NumOfStudent;i++) number[i]=stu[i].num;
QuickSort(number,stu,0,NumOfStudent-1);
Table_Head();
Data();
}
void Name_Sort(STUDENT stu[]) //功能7:按姓名的字典顺序排出成绩表
{
int i,j;
STUDENT temp;
for(i=0;i<NumOfStudent-1;i++)
for(j=i+1;j<NumOfStudent;j++)
{
if(strcmp(stu[i].name,stu[j].name)>0)
{
temp=stu[i];
stu[i]=stu[j];
stu[j]=temp;
}
}
Table_Head();
Data();
}
void QuickSort(int a[],STUDENT b[],int low,int high) //快速排序
{
int point;
if(low<high)
{
point=partion(a,b,low,high);
QuickSort(a,b,low,point-1);
QuickSort(a,b,point+1,high);
}
}
void swap(int a[],STUDENT b[],int low,int high)
{
int temp=a[low];
a[low]=a[high];
a[high]=temp;
STUDENT temp2;
temp2=b[low];
b[low]=b[high];
b[high]=temp2;
}
int partion(int a[],STUDENT b[],int low,int high)
{
int key=a[low];
while(low<high)
{
while(low<high&&a[high]>=key)
{
high--;
}
swap(a,b,low,high);
while(low<high&&a[low]<=key)
{
low++;
}
swap(a,b,low,high);
}
return high;
}
void Search_Number() //功能8:按学号查询学生排名及其考试成绩
{
long int number[100],key;
int i,j,l,r,mid;
for(i=0;i<NumOfStudent;i++) number[i]=stu[i].num;
QuickSort(number,stu,0,NumOfStudent-1);
printf("Enter the number you want to search:\n");
scanf("%ld",&key);
l=0; r=NumOfStudent-1;
while(l<r)
{
mid=(l+r)/2;
if(key==number[mid])
{
Table_Head();
printf("%8ld %s ",stu[mid].num,stu[mid].name);
for(j=0;j<NumOfCourse;j++) printf(" %2d ",stu[mid].score[j]);
printf("\n");
printf("rank: %d\n",stu[mid].rank);
return;
}
if(key>number[mid]) l=mid+1;
if(key<number[mid]) r=mid-1;
}
if(key==number[r])
{
Table_Head();
printf("%8ld %s ",stu[r].num,stu[r].name);
for(j=0;j<NumOfCourse;j++) printf(" %2d ",stu[r].score[j]);
printf("\n");
printf("rank: %d\n",stu[r].rank);
return;
}
printf("Not found!\n");
}
void Search_Name() //功能9:按姓名查询学生排名及其考试成绩
{
char key[100];
int i,j,k,len,flag,count=0;
printf("Enter the name you want to search:\n");
scanf("%s",key);
len=strlen(key);
for(i=0;i<NumOfStudent;i++)
{
for(j=0;j<=strlen(stu[i].name)-len;j++)
{
flag=1;
for(k=j;k<j+len;k++)
{
if(stu[i].name[k]!=key[k-j])
{
flag=0;
break;
}
}
if(flag)
{
if(!count) Table_Head();
count++;
printf("%8ld %s ",stu[i].num,stu[i].name);
for(k=0;k<NumOfCourse;k++) printf(" %2d ",stu[i].score[k]);
printf("\n");
printf("rank: %d\n",stu[i].rank);
break;
}
}
}
if(!count) printf("Not found!\n");
}
void Statistic() //功能10:按优秀(90~100)、良好(80~89)、中等(70~79)、及格(60~69)、不及格(0~59)5个类别,对每门课程分别统计每个类别的人数以及所占的百分比
{
int i,j,a,b,c,d,e;
printf(" A B C D E\n");
for(i=0;i<NumOfCourse;i++)
{
printf("Course%d ",i+1);
a=b=c=d=e=0;
for(j=0;j<NumOfStudent;j++)
{
switch(stu[j].score[i]/10)
{
case 10:
case 9:a++;
break;
case 8:b++;
break;
case 7:c++;
break;
case 6:d++;
break;
default:e++;
}
}
printf("%2d(%.1f%%) ",a,(double)a/NumOfStudent*100);
printf("%2d(%.1f%%) ",b,(double)b/NumOfStudent*100);
printf("%2d(%.1f%%) ",c,(double)c/NumOfStudent*100);
printf("%2d(%.1f%%) ",d,(double)d/NumOfStudent*100);
printf("%2d(%.1f%%)\n",e,(double)e/NumOfStudent*100);
}
}
void Output_Data() //功能11:输出每个学生的学号、姓名、各科考试成绩,以及每门课程的总分和平均分
{
Table_Head();
Data();
Course_Score(stu);
}
void wfile() //功能12:将每个学生的记录信息写入文件
{
FILE *fout;
int i,j;
fout=fopen("E://output.txt","w");
if(fout==NULL)
{
printf("Failed to write!\n"); //文件打开失败的处理
exit(0);
}
fprintf(fout,"IDnumber Name ");
for(i=1;i<=NumOfCourse;i++) fprintf(fout,"Course%d ",i);
fprintf(fout,"\n");
for(i=1;i<=NumOfCourse*9+14;i++) fprintf(fout,"-");
fprintf(fout,"\n");
for(i=0;i<NumOfStudent;i++)
{
fprintf(fout,"%8ld %s ",stu[i].num,stu[i].name);
for(j=0;j<NumOfCourse;j++) fprintf(fout," %2d ",stu[i].score[j]);
fprintf(fout,"\n");
}
fclose(fout);
}
void rfile() //功能13:从文件中读出每个学生的记录信息并显示
{
FILE *fin;
int i,j;
fin=fopen("E://input.txt","r");
if(fin==NULL)
{
printf("Failed to open a file!\n"); //文件打开失败的处理
exit(0);
}
fscanf(fin,"%d",&NumOfStudent);
fscanf(fin,"%d",&NumOfCourse);
stu=(STUDENT*)calloc(NumOfStudent,sizeof(STUDENT));
Total=(int*)calloc(NumOfStudent,sizeof(int));
for(i=0;i<NumOfStudent;i++)
{
fscanf(fin,"%ld",&stu[i].num);
fscanf(fin,"%s",stu[i].name);
for(j=0;j<NumOfCourse;j++)
{
fscanf(fin,"%d",&stu[i].score[j]);
Total[i]+=stu[i].score[j];
}
}
QuickSort(Total,stu,0,NumOfStudent-1);
for(i=NumOfStudent-1;i>=0;i--) stu[i].rank=NumOfStudent-i;
Table_Head();
Data();
}
int main()
{
A:
Menu();
switch(Input_Choice())
{
case 1:Input_Data();
system("pause");
system("cls");
goto A;
case 2:Course_Score(stu);
system("pause");
system("cls");
goto A;
case 3:Student_Score(stu);
system("pause");
system("cls");
goto A;
case 4:DownSort(Total);
system("pause");
system("cls");
goto A;
case 5:UpSort(Total);
system("pause");
system("cls");
goto A;
case 6:ID_Sort(stu);
system("pause");
system("cls");
goto A;
case 7:Name_Sort(stu);
system("pause");
system("cls");
goto A;
case 8:Search_Number();
system("pause");
system("cls");
goto A;
case 9:Search_Name();
system("pause");
system("cls");
goto A;
case 10:Statistic();
system("pause");
system("cls");
goto A;
case 11:Output_Data();
system("pause");
system("cls");
goto A;
case 12:wfile();
system("pause");
system("cls");
goto A;
case 13:rfile();
system("pause");
system("cls");
goto A;
default:free(stu);
free(Total); //动态数组内存的释放
exit(0);
}
return 0;
}
当我们的程序只需执行单个功能时,我们也许无需太过在意内存空间的占用、变量或函数的命名这些细节问题;但一旦我们需要编写一段执行多种功能的复杂程序,这些细节的重要性就凸显出来了。在这次的编程过程中,我会有意识地去使用一些英文单词加“_”这样的组合去命名一个变量或者是函数名,尽管这样的名称会显得相对较长,但却能随时提醒我变量、函数的作用,给我的编程带来方便。编程时,思维的连贯性是很重要的,我不希望这些小细节影响了整个思维的连贯性。另外,加注释也有助于编程,当一个函数名旁边有了注释的时候,我就会很清楚这个函数的作用是什么,编写起来就相对比较容易。
也同样是由于程序复杂性的增加,我们自行发现代码bug的难度上升了。我一般采用增加中途输出的方法对程序的问题或者漏洞进行定位。在这一过程中,我发现很多错误都发生在一些简单的算法上,而由于思维的习惯,我们一般会以为简单的地方不会犯错,这无形中又增加了发现bug的难度。通过几次痛苦的debug,我明白了要不断增加对于简单算法的掌握程度与熟练度,这会大大提升调试代码的速度,也会让我在编写较长代码时变得更轻松。
在调试程序过程中,我还注意到,分模块调试是最有效的一种调试方法。先通过输出的数据猜测可能出错的模块,再针对单个模块进行调试。我觉得这一点对我写程序的顺序也有所启发。比如,如果我实现知道在某段代码中我可能要用到一个我之前不太熟悉的算法或者语句,那我可以先用一些可以实现同样功能的语句替代它们的功能,待程序整体调试完成,再将这些复杂的语句或者是程序块添加到程序中,如果测试有问题,我也会明确知道问题发生的具体语句。
说了那么多有关调试的问题,调试本身也是极为重要的。很少有人能一遍就把代码写得很完美、滴水不漏,这就需要我们能够及时找出问题所在并迅速想出解决方法,有时甚至需要我们自己去发现自己程序的漏洞并尝试修复它。敲完代码绝不意味着程序的完成,调试往往意味着更严峻的考验。