成绩管理系统
设计要求
1.要有登录界面,登录用户分为管理员用户和普通用户。
2.普通用户要求有:自己的所有学科成绩和学号、查询自己单个学科成绩、查询科目信息、退出。
3.管理员用户需要有:查看所有学生成绩、根据学号查询单个学生所有信息、给某个学生的某一科目打分、添加科目、删除科目。
(整个系统要求具有记忆能力,即关闭后的数据下次打开还在。)
设计思路
源码分析及运行截图
头文件及函数、变量的定义
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void index();
void administrator_login();
void student_login();
void student_system();
void administrator_system();
void my_score();
void score_find();
void list();
void numquery();
void insert();
void del();
void update();
void score_remond();
void readfile();
void savefile(); //函数声明
管理员、学生账号和学生信息结构体
这一部分我采用了三个结构体数组分别存放管理员账号密码、学生账号密码及学生信息,结构体数组格式定义格式如下,管理员账号密码和学生账号密码的结构体数组我在定义时就进行了初始化,学生信息的结构体采用读取文件的方式得到数据,所以没有进行初始化,具体代码可以参见放在末尾的源码文件。
//结构体数组的定义格式
struct 结构体名
{
成员列表
}数组名[数组长度];
or
结构体类型 数组名[数组长度];
主函数
主函数部分结构很简单,通过readfile()函数读取预选设置好的txt文件数据到结构体数组,再打开菜单进入程序。
/*主函数*/
void main()
{
readfile();
index();
}
管理员登录和学生登录
按照设计要求,我们要对管理员和学生分别进行登陆操作,所以才有了这里的两个登录函数,虽然内容不一样,但结构都是一样的。
首先定义一个整型变量用于对所有登录的账号密码信息进行for循环判断,只有当账号密码与结构体数组初始化数据一样才能继续,如果连续输入三次错误,退出系统,这里注意账号和密码被看做是字符型,所以进行判断时用了string.h头文件中的strcmp()函数比较输入的字符串和初始化的字符串,对于连续三次错误时退出程序,我使用了系统级的exit(1)退出进程。运行效果展示如下:
管理员管理和学生成绩查询系统菜单
因为设计要求管理员和学生分开登录,所以我们就需要登录后进入两个不同的系统菜单,这是理解并完成这一步的关键,至于系统菜单的具体实现比较简单,涉及的知识也不多,就不做过多赘述了,相关代码和运行效果如下,但我很想吐槽一句,强迫症晚期的我为了保证菜单开头格式的整齐搞了半天,,,天那,细思极恐,,哈哈
/*管理员成绩管理系统菜单*/
void administrator_system()
{ int y;
printf("******************************欢迎来到学生成绩管理系统***************************\n");
printf("---------------------------------------------------------------------------------\n");
printf(" 选择要进行的操作\n");
printf(" 1 查看所有学生成绩\n 2 按学号查询单个学生成绩\n\
3 添加学生成绩\n 4 删除学生成绩\n 5 修改学生信息\n 6 保存并退出\n");
printf("------------------------------------------------------------------------------------\n");
scanf("%d",&y);
if(y==1) list();
if(y==2) numquery();
if(y==3) insert();
if(y==4) del();
if(y==5) update();
if(y==6) savefile();
}
学生查询个人所有成绩
这里我们要得到某一个人的全部信息,所以必须让程序知道该把哪一组数据呈现给我,我的想法很简单,通过你告诉我你的学号,我判断你是几号学生,然后把结构体数组的相关数据呈现给你,这里要说一下,因为数组里面有多组数据,系统不能像你吃铁板烧选菜一样挑着找,它必须一个个的确认是不是它找的对象,所以我们通过for循环遍历数组内容。
/*查询个人所有成绩*/
void my_score()
{
int x,i;
printf("为保证信息安全,你需要输入学号进行验证!\n");
printf("请输入学号:");
scanf("%d",&x);
printf("学号 姓名 专业 班级 语文 数学 英语\n");
for(i=0;i<n;i++) //for循环遍历数组内容
if(x==stu[i].num)
{ printf("%4d %s %s %4d %.1f %.1f %.1f\n",stu[i].num,stu[i].name,stu[i].sub,stu[i].classs,stu[i].chinese,stu[i].math,stu[i].English); break;}
if(i==n) printf("没有这个成绩纪录\n");
printf(" 1 返回菜单\n 2 继续查询\n");
scanf("%d",&x);
if(x==1) student_system();
else numquery();
}
查找某一科目成绩(按学号查找成绩)
这里的查找输出原理跟上面的查询是一样的,就不多说了,这里我单纯的是输出了成绩,有兴趣的可以在这里加一个成绩等级的判断,先告诉你单科成绩再告诉你该科目的等级评价A+、A、B、C。运行效果:
显示全部学生信息
当我们程序打开的时候,保存在txt文档中的信息已经被读取进了数组,我们想要输出全部学生信息,也就是输出数组里的全部元素,这样一样这里是不是就很简单了,是的,for循环遍历数组,然后把遍历到的所有信息按格式输出就行了,需要注意的是因为我们的程序加入了读写功能,导致了数组元素会发生改变,我们在循环的时候是i<n,而不是初始化时数组元素。
添加学生成绩
添加学生成绩,从程序的角度来说就是给结构体数组添加元素,这里我们再次定义在结构体里面定义过的学生信息变量,目的是把输入的新的学生信息存放起来,然后传递给学生信息结构体数组,因为学生信息的数据类型的特殊化,我依次使用了%d、%s、%s、%d、%f、%f、%f对输入信息赋值给定义的数组,需要注意的是把字符型变量放到数组中时要用到**strcpy(a,b);**作用是把字符b赋值给字符a,此部分代码:
/*添加学生成绩函数*/
void insert()
{ int x;
int num;
char name[20];
char sub[20];
int classs;
float chinese;
float math;
float English;
printf("请输入要添加的学号:");
scanf("%d",&num);
printf("请输入要添加的姓名:");
scanf("%s",&name);
printf("请输入要添加的专业:");
scanf("%s",sub);
printf("请输入要添加的班级号:");
scanf("%d",&classs);
printf("请输入要添加的语文成绩:");
scanf("%f",&chinese);
printf("请输入要添加的数学成绩:");
scanf("%f",&math);
printf("请输入要添加的英语成绩:");
scanf("%f",&English);
stu[n].num=num; //放入学号
strcpy(stu[n].name,name);
strcpy(stu[n].sub,sub); //strcpy复制字符串
stu[n].classs=classs;
stu[n].chinese=chinese;
stu[n].math=math;
stu[n].English=English;
n++; //实际人数增1
printf("***************成绩添加成功!\n");
printf(" 1 返回菜单\n 2 继续添加\n");
scanf("%d",&x);
if(x==1) administrator_system();
if(x==2) insert();
}
删除学生成绩
作为成绩管理系统,有增就必有删,如何删除指定的学生信息是我们该函数的要实现的功能,我的思路是首先通过管理员输入的学号找到要删除的学生的信息,然后把他后面的一个学生信息赋给他,依次下去直到最后一个学生信息,并将总人数减一,以便进行删除操作后的程序能在下次打开时正常运行,这里主要用到的有for循环、if…else判断、数组赋值,具体程序:
/*删除学生信息*/
void del() //删除学生信息
{
int i,m,a,x;
printf("请输入要删除信息的学生学号:\n");
scanf("%d",&a);
for(i=0;i<n;i++){
if(a==stu[i].num){
if(i==n-1){
n--;
break;
}
else{
for(m=i;m<n-1;m++){
stu[m].num=stu[m+1].num;
stu[m].classs=stu[m+1].classs;
stu[m].chinese=stu[m+1].chinese;
stu[m].math=stu[m+1].math;
stu[m].English=stu[m+1].English;
strcpy(stu[m].name,stu[m+1].name);
strcpy(stu[m].sub,stu[m+1].sub);}
n--;
break;
}
}
}
printf("***************成绩信息删除成功!\n");
printf(" 1 返回菜单\n 2 继续添加\n");
scanf("%d",&x);
if(x==1) administrator_system();
else del();
}
修改学生信息
这一段因为涉及到的变量的比较多,所以定义的用于中转修改数据的变量较多、因为姓名和专业是字符,所以定义了两个数组用于存放修改的变量的值,具体实现思路是请管理员输入要修改信息的学生学号,通过循环遍历找到该学号学号学生的信息,提示用户按要求输入要修改的信息和修改后的值,将新值存放在我们定义的变量中后再赋值给学生信息结构体数组中的相关部分,其中定义的变量相当于一个数据的中转站,对于成绩的修改,因为成绩涉及到了三个不同的科目,为了便捷,我将成绩的修改单独写在了score_remond函数里,方便程序在需要的时候引用.
文件读取和存储
这一部分我可真想说道一下了,因为平时练习的都是给程序手动输入数据,所以对于文件读取简直是生疏到了极点,为了实现这个功能可是颇费工夫了。经过一番挣扎,我决定使用格式化存取函数fscanf()和fprintf(),下面是我找到的资料
与示例不同的是我需要进行的是对结构体数组元素的存取,起初我想到了循环查找文件里面的元素,但具体怎么操作还是不太明白,经过参照这位大佬的CSDN文章讲解才算是有了思路。原文地址点这里。
按照这个思路来写就没错了,但我想提醒一下大家,也是我写的时候遇到的问题,保存数据的txt文档除了数据格式外还要注意编码格式是ANSI而不是UTF-8,不然程序在存取数据的时候会乱码!!这一部分的读取和保存原理都是一样的,只要懂了格式化赋值函数就OK了。我的代码供参考;
/*读取文件*/
void readfile()
{
FILE *fp = fopen("xinxi.txt", "r");
int i=0;
if(fp == NULL)
{
printf("***************文件打开失败!\n");
exit(0);
}
while(fscanf(fp,"%4d%s%s%4d%f%f%f\n",&stu[i].num,&stu[i].name,&stu[i].sub,&stu[i].classs,&stu[i].chinese,&stu[i].math,&stu[i].English)!=EOF)
{
i++;
}
fclose(fp);
printf("***************数据读取成功!\n");
}
/*保存文件*/
void savefile()
{
FILE *fpp = fopen("xinxi.txt", "w");
int j;
if(fpp == NULL)
{
printf("文件打开失败!\n");
exit(0);
}
for(j=0;j<=n;j++){
fprintf(fpp,"%4d %s %s %4d %.1f %.1f %.1f\n",stu[j].num,stu[j].name,stu[j].sub,stu[j].classs,stu[j].chinese,stu[j].math,stu[j].English);
}
fclose(fpp);
printf("***************数据保存成功!\n");
exit(0);
}
总结反思
哎呦喂,代码太长了吧,终于写完了,也是最近才试着写博客,程序写的比较赶,很多地方不够好,而且感觉没太讲解清楚,但程序本身并不难,有兴趣的同学网盘提取文件,自行编译试试呗。
链接:https://pan.baidu.com/s/1F_85hCFuv27kMSBI9Xj4jQ
提取码:x4wq