学生信息管理系统,众所周知,是c语言初级部分典型的课题。实际实现的方法其实还可以使用链表和文件来极大简化对获取的信息的操作过程,但是对于c初学者,很多东西并没有学过,这样就可以试一试单纯使用结构体来完成这样的小设计。
这样,首先就需要至少掌握结构体的知识,然后这样的小项目可以试着先都写在一个.c文件中,最后再自己尝试着进行分文件为一个main.c文件和若干.c和.h文件,再尝试学习编写Makefile来进行多文件编译,在这个过程中不断发现和解决问题。
ps.一定要先确实掌握结构体,结构体数组,函数中的传参,全局变量作用域等知识
一.设计构思和简单的准备工作
首先,要确定一个什么样的结构体,就需要看我需要什么功能。
于是,我写下来一个我想要的,能做xxx功能的,可能还很美观的学生信息管理系统的目录,既要用它,还可以拿他作为大纲。
示例:
void menu()
{
printf("****************【欢迎使用学生信息管理系统】******************");
printf("\n");
printf("\t\t\t<<<目录>>>\n(若在使用过程中想返回本页面请在结束操作后刷新页面)\n");
printf("提示:请输入您想使用的服务前的编号。\n\n");
puts("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓");
puts("┃ \t\t学生信息管理系统—————帮助菜单 ┃");
puts("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓");
puts("┃ \t\tH = 显示帮助菜单 ┃"); //数字触发还是字符触发看个人兴趣
puts("┃ \t\tT = 成绩录入 ┃");
puts("┃ \t\tA = 计算学生平均分 ┃");
puts("┃ \t\tL = 列出成绩表 ┃");
puts("┃ \t\tP = 按平均成绩由高到低排序 ┃");
puts("┃ \t\tS = 按学号查询学生成绩 ┃");
puts("┃ \t\tC = 清屏 ┃");
puts("┃ \t\tQ = 退出系統(有惊喜呦o((>ω< ))o) ┃");
puts("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛");
puts("┃ \t Copyright <c> 2023.2.6 By 夢靈子DMC\t ┃");
puts("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛");
}
运行实现:
这里说一个问题,我使用的代码编辑器是VScode,虚拟机是ubuntu16版本,在制作目录时,很容易输完汉字,再去对其用心设计的符号边框时(比如 |,*,·等等),可能会产生上下不同行符号无法对其的情况,究其原因,还是汉字的占位大小问题,如果有同样问题,这里可以尝试修改一下字符编码,一般汉字都用UTF-8编码,当然,也可能对不齐的问题不在于此,最简单的解决办法还是在外部别的环境里打出已经对齐好的条条框框,再转移到VScode里运行。
其实目录的实现和结构体的设计哪个都可以先进行,因为这两部分都是围绕着你设想的功能来进行设计,所以看自己需要来就好
然后我根据这些需要,设计一个结构体:
struct student
{
int id; //学生id
float CN; //语文成绩
float EN; //英语成绩
float MT; //数学成绩
float AVG; //平均分
};
这里当然也可以加一个char name[ ]的字符数组来进行学生姓名的存储,但这里我只使用纯数结构体来做更直观更简单的展示,有需求自己添加即可。
当然,要是还有需求,也可以typedef一下为结构体取个简短的别名
我们存放学生数据肯定不是一个人两个人的数据,而是一个可以根据我们需要写入的数量,所以我们需要一个结构体数组来存放我们输入学生人数的信息
int num;
scanf (”%d“,&num);
struct student stu[num];
或者直接设定为
struct student stu[100]; //一个比较大的数量的数组
实际学生人数num也可以不用来构造数组,而是去取代循环条件,作为其他函数执行的一个循环次数的约束。
无论用何种方式,可以声明全局变量来实现在全文件全函数之中的使用
这样,我们就设计好了结构体,声明好构造的结构体类型(一般做全局声明),也就可以开始功能部分的实现。
二.功能实现
简单的学生信息管理系统,优先使用while(1)循环在主函数中做为系统持续运作的“动力源”,switch..case 作为功能实现的交互接口。由于我们在实现功能过程中,基本是在进行单次单功能指令的操作(比如编号1,2,3的功能,或者编号A,B,C的功能),最终还是对终端输入进来的命令进行一种反馈,所以switch这样的选择结构,确实非常吻合我们的需要。
printf("请输入您想使用的服务(指令字母编号):");
scanf("%c",&t);
i=turn(t);
/*这里我另写了一个turn函数,将输入的字符指令转为数字,以让
switch在实现功能书写时更加明确。
当然,也可以保留字符的写法
case 'A':
*/
switch(i)
{
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
break;
case 6:
break;
case 7:
break;
default:printf("error!错误提示:无此功能,请重新输入\n");break;
}
}
//个人喜好
接下来,就要根据每个模块的需要写函数了。
①菜单:
case 0: {
menu(); //自己写的功能函数
getchar(); //实际输出时发现的循环中存在垃圾字符干扰下一轮scanf
break;
}
②录入成绩:
struct student
{
int id;
float CN;
float EN;
float MT;
float AVG;
};
struct student stu[50];
int num =0; //每次存放的人数
int num2=0; //第二次输入开始要记录目前已经录入的人数
int Type(int a)
{
char rec;
int counter=0;
if(num2!=0)
{
counter=num2;
}
while(counter<a)
{
printf("请输入信息:\n学号\t语文\t 英语\t数学\n");
while(counter<a)
{ printf("学号:");
scanf("%d",&(stu[counter].id));
printf("语文:");
scanf("%f",&(stu[counter].CN));
printf("英语:");
scanf("%f",&(stu[counter].EN));
printf("数学:");
scanf("%f",&(stu[counter].MT));
counter++;
}
}
if (counter==a)
num2=num;
return 0;
}
③计算平均分:
void AVER()
{
for(int i=0;i<num;i++)
{
stu[i].AVG=(stu[i].CN+stu[i].EN+stu[i].MT)/3;
}
printf("学生成绩平均值为:\n");
printf("学号\t平均值\n");
for(int j=0;j<num;j++)
printf("%d \t%.2f \n",stu[j].id,stu[j].AVG);
}
④打印成绩表:
case 3:{
if(stu[0].id==0)
{
printf("成绩表为空!请先使用命令 T 录入学生成绩。\n");
getchar();
function();
break;
}
else{
puts("成绩表如下:");
puts("学号\t语文\t英语\t数学\n");
for(int j=0;j<num;j++)
{
printf("%d \t%.1f\t %.1f\t %.1f\n",stu[j].id,stu[j].CN,stu[j].EN,stu[j].MT);
}
getchar();
function();
break;
}
}
⑤根据平均分排序并打印:
void range()
{
int i,j,max;
int a[num];
struct student copy[50];
struct student tem;
for(int k=0;k<num;k++)
{
copy[k]=stu[k];
}
for(i=0;i<num-1;i++)
{
for(j=0;j<num-1-i;j++)
{
if(copy[j].AVG<copy[j+1].AVG)
{
tem=copy[j]
copy[j]=copy[j+1]
copy[j+1]=tem;
}
}
}
printf("平均成绩排名为:\n学号\t平均成绩\n");
for(int m=0;m<num;m++)
printf("%d \t%.2f\n",copy[m].id,copy[m].AVG);
}
⑥根据学号查找人信息:
void idscan(int a)
{
for(int i=0;i<num;i++)
{if(stu[i].id==a&&stu[i].AVG==0)
{
puts("学号\t语文\t英语\t数学\n");
printf("%d \t%.1f\t %.1f\t %.1f\n",stu[i].id,stu[i].CN,stu[i].EN,stu[i].MT);
}
else if(stu[i].id==a&&stu[i].AVG!=0)
{
puts("学号\t语文\t英语\t数学\t平均分\n");
printf("%2d\t%.1f\t%.1f\t%.1f\t%.2f\n",stu[i].id,stu[i].CN,stu[i].EN,stu[i].MT,stu[i].AVG);
}
}
}
⑦清空:
case 6: {
system("clear");
getchar();
function();
break;
}
⑧退出:
case 7: {
exit(1);
break;
}
分析:
这里有一些问题集中分析一下。
我们在写代码的过程中,会遇到很多逻辑上的问题,比如因果逻辑,或者时间先后逻辑,是非逻辑等等。
就比如我们所设计的这个信息系统,假如我们没有录入成绩,我们一开始就来执行打印或者计算平均分,这样是不是不符合因果先后的逻辑呢?
也就是说,我们需要先有成绩表信息,才能对这些信息进行操作,所以,我们需要进行判定,我们的结构体数组里有没有东西?我没执行过计算平均分时打印成绩表还需要打印平均分么?
很多东西需要根据自己的逻辑理解来分析,来使设计更加合理,这其实也是一种站在用户角度的测试-反馈-修改的模式。
另外,牵扯到了一个指令:system和exit。
在window环境下,我们需要调用 system.h来使用一些系统函数,比如
system("pause"); //让程序暂停一下
system("cls"); //清屏
而在linux下,则是完全不同的stdlib.h,以及有更多需要还有
#include <signal.h>
#include <unistd.h>
这样我们可以使用譬如如下指令:
system("clear"); //清屏
exit(1); //退出
**最后,当我们基本框架都完成了,就可以再在当前的基础上对系统进行润色(甚至整活)
所以我们可以写更多的功能,更多的函数模块,(甚至整活!!!!)
这里我选择了比较娱乐的增光添彩的方法,大家只要满足自己需求即可
三.功能部分完成
void function()
{
int i;
char t;
char a[10];
int b=1;
printf("请输入您想使用的服务(指令字母编号):");
scanf("%c",&t);
i=turn(t);
switch(i)
{
case 0: {menu();
getchar();
break;}
case 1: {
printf("请输入学生人数:");
scanf("%d",&num);
if(num2!=0)
{num=num2+num;}
while(b)
{
b=Type(num);
}
getchar();
getchar();
function();
break;
}
case 2: {if(num==0)
{
printf("成绩表为空!请先使用命令 T 录入学生成绩。\n");
getchar();
function();
break;
}
else
{
AVER();
}
getchar();
function();
break;
}
case 3:{
if(stu[0].id==0)
{
printf("成绩表为空!请先使用命令 T 录入学生成绩。\n");
getchar();
function();
break;
}
else{
puts("成绩表如下:");
puts("学号\t语文\t英语\t数学\n");
for(int j=0;j<num;j++)
{
printf("%d \t%.1f\t %.1f\t %.1f\n",stu[j].id,stu[j].CN,stu[j].EN,stu[j].MT);
}
getchar();
function();
break;
}
}
case 4: {
if(num==0)
{
printf("成绩表为空!请先使用命令 T 录入学生成绩。\n");
getchar();
function();
break;
}
else
{
range();
getchar();
function();
break;
}
}
case 5: {
int idnum;
if(num==0)
{
printf("成绩表为空!请先使用命令 T 录入学生成绩。\n");
getchar();
function();
break;
}
else
{
printf("请输入学生学号:");
scanf("%d",&idnum);
printf("\n");
idscan(idnum);
getchar();
function();
break;
}
}
case 6: {
system("clear");
getchar();
function();
break;
}
case 7: { meme2();
meme();
exit(1);break;}
default:printf("error!错误提示:无此功能,请重新输入\n");break;
}
}
四.完成效果
//这里确实没想到打印盲文能有这么好的效果,如果有兴趣,像这样整点活也是繁杂任务中的乐趣之一,这样的盲文表情包可以从各大论坛下以及steam评论区获取(生草www)。
//当然,如果你是交作业或者什么的,还可以来点有”油腻的设计“,比如
//这是我大一c语言课程设计写的,答辩时候也确实管用(不是)
(ps.牢骚话.这里用的文档编辑器是notepad - -,也算支持一波国产了,或者用notepad3之类的,某++整的浑水不趟也罢,开源且又不是不可替代,个人觉得虽然对客观现实不会有太大影响,但是是一个思想和信念的问题,确实该硬气点的。)
五.尾声
这是未使用数据结构以及C高级知识的,纯C语言的,仅使用结构体完成的,适合初学者的学生信息管理系统,由于本人也是在求学道路上的一员,也会在学习中发现很多问题,也很理解同僚。本身想吃计算机及相关领域这碗饭,既要有探求心、会主动探求(方法最重要,首先自行百度总会吧...吧..吧?),又不能有玻璃心,这行本身没那么多所谓”人情世故“,但作为程序员或者开发者、工程师那种对自己自信、傲气以及对毫无关系的新人的无义务指导,都是乐而为之,否则避之,自己需要强大起来,才会有更多能交流上的大佬,否则只是不思进取,就只是在已经度过这个阶段的先行者们眼里的一个”无知麻瓜“罢了,问人问题不寒碜,但不要因为被冷语相向动摇,因为本身对方就没有对你负责的义务,会微积分的大学牲教院里小孩子加减乘除亦然,站在的层面不同,自然不容易理解对方的视界,哪怕自己也走过这条路,所以,人,天行健,自强不息,我也希望能遇见好心教导我批评我的错误的贵人,也希望自己有一天能成为足够强大的人。