乐愚社区Beta

 编程语言  >  从零开始完成C程序设计大作业——学生成绩管理系统

从零开始完成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


24条回帖
晨曦启明  L3  评论于
(1)  回复(2) 1#
不错,比较有技术
发表在2020-12-27 回复
linghua :数据库的增删改查,
发表在2021-01-08 回复
  
:)
y夜夜yeye  L3  评论于
(1)  回复(1) 2#
楼主,b站的id叫什么呀
爱学习的咸鱼君 楼主 :爱学习的咸鱼君
发表在2020-12-30 回复
  
:)
爱学习的咸鱼君 楼主  L0  评论于
(0)  回复(2) 3#
该如何修改昵称呢。当初注册随意打了个名字。。。
Walker :下载APP-我的设置里可以修改用户名
发表在2020-12-26 回复
爱学习的咸鱼君 楼主 :了解了
发表在2020-12-27 回复
  
:)
hello_3000  L0  评论于
(0)  回复(1) 4#
楼主可以把敲代码的过程录成视频吗
爱学习的咸鱼君 楼主 :我等级太低了,不能插入本地视频。之后会先上传B站,再把视频链接复制过来。
发表在2020-12-24 回复
  
:)
fggf  L0  评论于
(0)  回复(0) 6#
厉害啊!
1365076241  L0  评论于
(0)  回复(1) 7#
昨天社团c语言大作业答辩,一堆bug
发表在2020-12-28 回复
  
:)
fdc  L1  评论于
(0)  回复(1) 9#
我给你爆赞
爱学习的咸鱼君 楼主 :谢谢
发表在2020-12-30 回复
  
:)
青烟雨  L17  评论于
(0)  回复(1) 10#
这个是成绩录入的吗?表示没看懂
爱学习的咸鱼君 楼主 :是的
发表在2021-01-04 回复
  
:)
梅丹佐  L0  评论于
(0)  回复(0) 12#
好家伙,图形界面这个哪里可以学,c语言学了好多,基础的一些东西都差不多了,看见这个图形界面,想学一下
小米米小life  L2  评论于
(0)  回复(0) 13#
大佬,好爱你(抱大腿)
3275799598  L0  评论于
(0)  回复(0) 14#
关注了老铁
还没注册帐号?快来注册社区帐号,和我们一起嗨起来!
关于本社区

集各类兴趣爱好于一身的轻量化交流社区,在此您可以和他人一起分享交流您觉得有价值的内容,社区鼓励大家发表原创内容,为社区添砖加瓦!

发帖奖励 → 社区版规 → 招聘版主 →
推荐版块
扫描二维码下载社区APP
回到顶部