精
从零开始完成C程序设计大作业——学生成绩管理系统 |
|
爱学习的咸鱼君
L0
• 2020-12-24 • 回复 24 • 最后编辑于2021-01-06 01:49 • 只看楼主
• 举报
|
前言
学生成绩管理系统可以说是C语言程序设计的结课的必备大作业了。花了些时间,费了些头发肝了下,完成了两个系统,一个是控制台版本的,另一个用easyx图形库进行了优化。 先放出完成后的演示图片占个坑。具体的实现过程,等我再梳理下,再慢慢更新整理到论坛上来。
演示DEMO
基础控制台版本
EasyX带界面版本
控制台版本开发过程整理
更新时间: 12-24
- 初始界面的处理
- 功能分析及框架搭建
12-25
- 文件输入
- 学生信息输出
- 学生信息查找
12-26
- 增加学生信息
- 数据的保存
- 学生信息的修改
- 学生信息的删除
12-27
- 结束界面
开发环境
系统: win10
IDE: Dev Cpp
前置知识
需要掌握基础的C语言知识
- 顺序结构
- 分支结构
- 循环结构
- 数组、字符串
- 函数
- 结构体、指针
- 链表
- 文件操作
功能分析
工欲善其事必先利其器,先分析好整体功能和大体的布局再慢慢动手进行代码的实现。 基础设想是,先显示主菜单,通过输入数字选择对应的功能,包括有增加学生信息,删除学生信息,修改学生信息,查询学生信息以及退出程序功能。
主菜单界面实现
使用输出语句来实现界面。计划使用数字键来代表各自的功能。
//主菜单界面
void welcome(){
printf("************************\n");
printf("** 学生成绩管理系统 **\n");
printf("** 作者:咸鱼君 **\n");
printf("** **\n");
printf("** 增加学生信息 ---1 **\n");
printf("** 删除学生信息 ---2 **\n");
printf("** 修改学生信息 ---3 **\n");
printf("** 查询学生信息 ---4 **\n");
printf("** 输出学生信息 ---5 **\n");
printf("** 退出管理系统 ---0 **\n");
printf("请输入对应的功能键(数字): ");
}
功能框架搭建
先处理好整个功能框架,通过输入数字,进行分支判断,不同的数字代表不同的功能。先将要实现的功能做个简易的版本出来,之后再慢慢填充细节。 想的是执行完某些功能后还能继续进行操作,所以将程序放入循环中,并在操作执行完成后,询问继续操作,再根据选择进行处理。
int main(){
int choice=0;
while(true){
welcome();
scanf("%d",&choice);
switch(choice){
case 1://增加学生信息
addStuInfo();
break;
case 2://删除学生信息
deleteStuInfo();
break;
case 3://修改学生信息
fixStuInfo();
break;
case 4://查询学生信息
searchStuInfo();
break;
case 5://输出学生信息
printStuInfo();
break;
case 0://退出程序
goodBye();
break;
}
printf("是否需要继续操作?(yes:1 / no:0 ):");
scanf("%d",&choice);
if(choice==0){
break;
}
}
return 0;
}
数据结构定义
围绕学生信息进行处理,那么思考学生具有哪些信息。包含,学号,姓名,性别,语文,数学,英语成绩,还有个总分。将对应属性集合在一块,采用结构体方式进行数据操作。并使用链表的方式将数据进行串联。
typedef struct Node{
int id;//学号
char name[30];//姓名
char sex[10];//性别
int ch;//语文
int ma;//数学
int en;//英语
int sum;//总分
struct Node *next;//指向下一个结点
}node;
文件数据的读取
这此程序的数据都要以文件的形式进行信息的保存,如果想要在屏幕上输出数据,那么得先读取文件中的信息才行。 C语言中文件读取操作要使用文件指针和相关函数,格式如下。
FILE *fpr=fopen("文件名","操作方式");
fscanf(fpr,"%d",&intValue);
文件名需要加上后缀名,操作方式因为是要从文件中读取信息,所以写r。如果是进行信息的写入则是w。 之后需要将读取的信息以链表的方式组织起来,打算采用尾插法的方式插入数据。
// 尾插法
t->next=s;//链表尾指针 的后一个结点指向新结点
t=s;//更新尾结点
t->next=NULL;//链表尾指针 的后一个结点指向NULL
读取函数
// 文件输入
int readFile(Node *L){
FILE *fpr=fopen("studentInfo.txt","r");
node *t=L;
node st;
node *s;
if(fpr==NULL){
return 0;
}else{
//fscanf()
while(fscanf(fpr,"%d %s %s %d %d %d %d",&st.id,st.name,st.sex,&st.ch,&st.ma,&st.en,&st.sum)!=EOF){
s=(node *)malloc(sizeof(node));
*s=st;
// 尾插法
t->next=s;//链表尾指针 的后一个结点指向新结点
t=s;//更新尾结点
t->next=NULL;//链表尾指针 的后一个结点指向NULL
}
}
fclose(fpr);//关闭文件指针
return 1;
}
输出所有学生信息
接下来完成所有学生信息的输出。此处需要考察链表的遍历。
void printStuInfo(node *L){
system("cls");
node *p=L->next;
printf("________________________________________________________\n");
printf("|学号\t|姓名\t|性别\t|语文\t|数学\t|英语\t|总分\t|\n");
printf("________________________________________________________\n");
if(p!=NULL){
while(p!=NULL){
printf("%d|%s\t|%s\t|%d\t|%d\t|%d\t|%d\t|\n",p->id,p->name,p->sex,p->ch,p->ma,p->en,p->sum);
printf("________________________________________________________\n");
p=p->next;
}
}
}
增加学生信息
接下来是增加学生的信息,此处采用头插法将链表结点进行插入。将学生信息的增加分成了两部分,一部分是界面的打印,一部分是底层数据的处理。
界面实现:
//增加学生信息
void printAddStuInfo(){
//
system("cls");
node st;
printf("请输入新增学生相关信息\n");
printf("学号:");
scanf("%d",&st.id);
printf("姓名:");
scanf("%s",st.name);
printf("性别:");
scanf("%s",st.sex);
printf("语文:");
scanf("%d",&st.ch);
printf("数学:");
scanf("%d",&st.ma);
printf("英语:");
scanf("%d",&st.en);
st.sum=st.ch+st.ma+st.en;
insertStuInfo(&List,st);
}
功能实现:
void insertStuInfo(node *L,node e){
//头插法
node *h=L;
node *s=(node *)malloc(sizeof(node));
*s=e;
s->next=h->next;
h->next=s;
}
文件数据的写入
这部分和文件的读取部分相似,思路是将整个链表内容存储到文件中。
使用fprintf()将文件信息进行存储。
//保存文件
int saveFile(node *L){
FILE *fpw=fopen("studentInfo.txt","w");
if(fpw==NULL) return 0;
node *p=L->next;
while(p!=NULL){
fprintf(fpw,"%d %s %s %d %d %d %d\n",p->id,p->name,p->sex,p->ch,p->ma,p->en,p->sum);
p=p->next;
}
fclose(fpw);//关闭文件指针
return 1;
}
再在学生信息的增加过程中添加文件数据的保存操作。
void insertStuInfo(node *L,node e){
//头插法
node *h=L;
node *s=(node *)malloc(sizeof(node));
*s=e;
s->next=h->next;
h->next=s;
//保存文件
saveFile(L);
}
学生信息查询
接下来是实现学生信息查询功能,计划也是页面输出部分与逻辑实现部分进行分离。打算,可以通过学号与姓名两个关键值进行信息的查找。因为是链表结构,为了方便之后的操作,逻辑函数会返回查找到的学生信息的前一个结点位置,这样的话也能在删除学生信息与修改学生信息中进行函数的复用了。
界面实现:
//查询学生信息
void printSearchStuInfo(node *L){
system("cls");
int choice=0;
int id;
char name[50];
node *st;
printf("按学号查询----- 1\n");
printf("按姓名查询----- 2\n");
printf("请输入查询方式:");
scanf("%d",&choice);
if(choice == 1){
printf("请输入要查询的学号:");
scanf("%d",&id);
st=searchStuInfoById(id,L);
if(st==NULL){
printf("查无此人!\n");
}else{
st=st->next;
printf("________________________________________________________\n");
printf("|学号\t|姓名\t|性别\t|语文\t|数学\t|英语\t|总分\t|\n");
printf("________________________________________________________\n");
printf("%d|%s\t|%s\t|%d\t|%d\t|%d\t|%d\t|\n",st->id,st->name,st->sex,st->ch,st->ma,st->en,st->sum);
printf("________________________________________________________\n");
}
}else if(choice ==2){
printf("请输入要查询的姓名:");
scanf("%s",name);
st=searchStuInfoByName(name,L);
if(st==NULL){
printf("查无此人!\n");
}else{
st=st->next;
printf("________________________________________________________\n");
printf("|学号\t|姓名\t|性别\t|语文\t|数学\t|英语\t|总分\t|\n");
printf("________________________________________________________\n");
printf("%d|%s\t|%s\t|%d\t|%d\t|%d\t|%d\t|\n",st->id,st->name,st->sex,st->ch,st->ma,st->en,st->sum);
printf("________________________________________________________\n");
}
}
}
逻辑实现:
思路是遍历整个链表,逐一对关键信息进行比较。
按学号进行查找,找不到返回NULL,找到了返回前一个结点位置
//按学号进行查找
node * searchStuInfoById(int id,node *L){
node *p=L;
while(p->next!=NULL){
if(p->next->id==id){
return p;
}
p=p->next;
}
return NULL;
}
按姓名进行查找,找不到返回NULL,找到了返回前一个结点位置
//按姓名进行查找
node * searchStuInfoByName(char name[],node *L){
node *p=L;
while(p->next!=NULL){
if(strcmp(name,p->next->name)==0){
return p;
}
p=p->next;
}
return NULL;
}
学生信息修改
依旧是分成两部分,先输出界面,过程逻辑的话就沿用学生信息查询的部分。实现逻辑是这样的:先查到要查询的学生信息,在对信息修改,改完了再保存到文件中。
页面和实现部分:
//修改学生信息
void printFixStuInfo(node *L){
system("cls");
int id;
int choice=-1;
printf("请输入要查找的学生学号");
scanf("%d",&id);
node *st=searchStuInfoById(id,L);
if(st==NULL){
printf("查无此人!");
return;
}
st=st->next;
while(1){
system("cls");
printf("________________________________________________________\n");
printf("|学号\t|姓名\t|性别\t|语文\t|数学\t|英语\t|总分\t|\n");
printf("________________________________________________________\n");
printf("%d|%s\t|%s\t|%d\t|%d\t|%d\t|%d\t|\n",st->id,st->name,st->sex,st->ch,st->ma,st->en,st->sum);
printf("________________________________________________________\n");
printf("修改姓名---- 1\n");
printf("修改性别---- 2\n");
printf("修改语文---- 3\n");
printf("修改数学---- 4\n");
printf("修改英语---- 5\n");
printf("请输入要修改的信息: ");
scanf("%d",&choice);
switch(choice){
case 1:
printf("请输入姓名:");
scanf("%s",st->name);
break;
case 2:
printf("请输入性别:");
scanf("%s",st->sex);
break;
case 3:
printf("请输入语文:");
scanf("%d",&st->ch);
break;
case 4:
printf("请输入数学:");
scanf("%d",&st->ma);
break;
case 5:
printf("请输入英语:");
scanf("%d",&st->en);
break;
}
st->sum=st->ch+st->ma+st->en;
printf("是否继续修改学生信息?(y-1 / n-0)\n");
scanf("%d",&choice);
if(choice == 0){
break;
}
}
printf("________________________________________________________\n");
printf("|学号\t|姓名\t|性别\t|语文\t|数学\t|英语\t|总分\t|\n");
printf("________________________________________________________\n");
printf("%d|%s\t|%s\t|%d\t|%d\t|%d\t|%d\t|\n",st->id,st->name,st->sex,st->ch,st->ma,st->en,st->sum);
printf("________________________________________________________\n");
//保存文件信息
saveFile(L);
}
学生信息删除
接下来实现学生信息删除部分。页面部分输出提示,之后输入学号查询要删除的学生信息。利用之前实现的查询信息的函数得到结点位置,之后再根据位置删除对应的结点,再将修改后的信息保存至文件中去。
页面部分
//删除学生信息
void printDeleteStuInfo(node *L){
system("cls");
int id;
node *p;
printf("请输入要查找的学生学号");
scanf("%d",&id);
node *st=searchStuInfoById(id,L);
p=st;
if(st==NULL){
printf("查无此人!");
return;
}
st=st->next;
printf("________________________________________________________\n");
printf("|学号\t|姓名\t|性别\t|语文\t|数学\t|英语\t|总分\t|\n");
printf("________________________________________________________\n");
printf("%d|%s\t|%s\t|%d\t|%d\t|%d\t|%d\t|\n",st->id,st->name,st->sex,st->ch,st->ma,st->en,st->sum);
printf("________________________________________________________\n");
deleteStuInfo(p);
saveFile(L);
}
结点删除部分
//删除学生信息
void deleteStuInfo(node *pr){
node *s=pr->next;
pr->next=s->next;
s->next=NULL;
free(s);//释放结点空间
}
结束界面
到此为止,这个小系统的基础功能都已经完成了。接下来把结束界面处理下。
//退出程序
void goodBye(){
system("cls");
printf("欢迎下次使用~\n");
exit(0);//结束程序
}
学生成绩管理系统V1.0版本到此也就完结了。之后也可以在此基础上进行其他功能的开发,比如,程序排序,最高分,平均分等等的处理。大体都是围绕链表的遍历,插入和删除操作来执行的。再结合自己对界面的设计即可。 剩余内容待整理上传
图形界面版本开发过程整理
更新时间
2020-12-24
- 开发环境
2020-12-28
- 图形库安装
- 创建新项目
2021-1-4
- 绘制界面
2021-1-6
- 按钮点击状态
开发环境
系统:Win 10
IDE:visual studio 2019
图形库: EasyX
安装EaxyX库
什么是EasyX
EasyX 是针对 C++ 的图形库,可以帮助 C 语言初学者快速上手图形和游戏编程。
比如,可以用 VC + EasyX 很快的用几何图形画一个房子,或者一辆移动的小车,可以编写俄罗斯方块、贪吃蛇、黑白棋等小游戏,可以练习图形学的各种算法,等等。
安装
首先去官网下载界面下载图形库。
双击下载下来的安装包
点击相应版本进行安装,建议把帮助文档也安装上。我的电脑上是visual studio2019。
新建项目并简单测试
打开VS2019并新建空白项目。
右击源文件并点击添加-新建项。
选择C++源文件,并修改名字为main
输入简单的测试文件,看能否运行图形库。
#include <graphics.h> // 引用图形库头文件
#include <conio.h>
int main()
{
initgraph(640, 480); // 创建绘图窗口,大小为 640x480 像素
circle(200, 200, 100); // 画圆,圆心(200, 200),半径 100
_getch(); // 按任意键继续
closegraph(); // 关闭绘图窗口
return 0;
}
出现下图说明能够正常使用。
解释下个函数的作用。
initgraph(width,height)
是初始化窗口并设定长宽大小。
closegraph
是在结束的时候关闭窗口用的。
界面设置
整体分成两个部分,左侧按钮功能区,右侧是信息显示区。
int main()
{
initgraph(WIDTH, HEIGHT); // 创建绘图窗口,大小为 640x480 像素
drawLeft();
drawRight();
_getch(); // 按任意键继续
closegraph(); // 关闭绘图窗口
return 0;
}
定义按钮结构体
//包含按钮放置的位置和按键信息
btNode leftMen[6] = {
{5,5,L" 作品信息 "},
{5,35,L"显示学生信息"},
{5,65,L"增加学生信息"},
{5,95,L"删除学生信息"},
{5,125,L"修改学生信息"},
{5,155,L"查询学生信息"}
};
绘制按钮
//绘制按钮功能区
void drawBtn(btNode t) {
setfillcolor(RGB(93, 107, 153));
setbkmode(TRANSPARENT);
fillroundrect(t.x, t.y, t.x + 120, t.y + 20, 10, 10);
outtextxy(t.x + 15, t.y + 2, t.text);
}
setfillcolor
是设定填充颜色。
setbkmode
是设置背景模式,选择了透明模式
fillroundrext
绘制圆角矩形,作为按钮的形状
outtextxy
在对应位置输出文字,显示按钮信息
绘制左侧功能按钮区
//绘制左侧功能按钮区
void drawLeft() {
SetWorkingImage(&leftImg);//设置绘制的图像
setbkcolor(RGB(93, 107, 153));//设置背景色
cleardevice();//清空图像
//绘制按钮
for (int i = 0;i < 6;i++) {
drawBtn(leftMen[i]);
}
SetWorkingImage();//将图像绘制到窗口上
putimage(0, 0, &leftImg);
}
绘制右侧显示区
//绘制右侧显示区
void drawRight() {
SetWorkingImage(&rightImg);//设置绘制的图像
setbkcolor(RGB(247, 249, 254));//设置背景色
cleardevice();//清空图像
SetWorkingImage();//将图像绘制到窗口上
putimage(161, 0, &rightImg);
}
增加按钮的点击状态
现在的按钮部分仅仅是个图像,要想产生点击的功能,还要配合对鼠标消息的处理。
想设置出的效果是这样的:按钮点击后,对应的颜色会发生改变。可以增加一个状态子属性,检测到点击信息后,修改对应状态值,在绘制按钮时,对状态值进行判断,不同的状态对应不同的颜色。
按钮结构体
typedef struct btNode{
int x, y;
wchar_t text[20];//内容
int status;//0-默认 1-按下
}btNode;
绘制按钮
//绘制按钮
void drawBtn(btNode t) {
if (t.status == 0)
setfillcolor(RGB(93, 107, 153));//设置填充颜色
else
setfillcolor(RGB(204, 213, 240));
setbkmode(TRANSPARENT);//设置背景模式为透明
//绘制圆角矩形作为按钮形状
fillroundrect(t.x, t.y, t.x + 120, t.y + 20, 10, 10);
outtextxy(t.x + 15, t.y + 2, t.text);//输出按钮信息
}
接下来就是对鼠标行为的检测判断了,这部分可以查看一下帮助文档,
在主函数中,修改逻辑,不断循环获取鼠标信息看是否按下左键,在判断鼠标的位置是否在对应的按钮区域内,在对应的区域就修改哪个属于的按键状态。
int main()
{
initgraph(WIDTH, HEIGHT); // 创建绘图窗口,大小为 640x480 像素
int i;
MOUSEMSG m;//定义鼠标信息
while (1) {
m = GetMouseMsg();//获取鼠标信息
if (m.uMsg == WM_LBUTTONDOWN) {
if (m.x >= 0 && m.x <= 160) {//鼠标在左侧功能按键区
for (i = 0;i < 6;i++) {
//判断鼠标位置是否在按钮范围内
if (m.x >= leftMen[i].x && m.x <= leftMen[i].x + 120 && m.y >= leftMen[i].y && m.y <= leftMen[i].y + 20) {
setUpLeftBtn();//初始化按钮状态
leftMen[i].status = 1;//修改对应按钮状态
}
}
}
}
drawLeft();//绘制左侧区域
drawRight();//绘制右侧区域
}
closegraph(); // 关闭绘图窗口
return 0;
}
初始化左侧按钮状态
// 初始化左侧按钮状态
void setUpLeftBtn() {
int i;
for (i = 0;i < 6;i++) {
leftMen[i].status = 0;
}
}
以上所有源码都已上传至Gitee码云上。 Fork me on Gitee 可以的话,给个star吧 XD