首页 » 技术分享 » 实现简单的学生选课信息管理系统

实现简单的学生选课信息管理系统

 

中山大学软件工程程序设计I 大作业。

要求

本系统模拟实现学生课程信息管理系统,其中包括学生信息,课程信息以及学生的选课信息(储存在文本文件当中),其中功能包括三部分:

学生相关功能

  1. 添加学生信息到学生信息文件当中,学生信息包括:学号(stuId),姓名(stuName),性别(stuSex)
  2. 删除学生信息
  3. 改变学生信息
  4. 查看学生信息

课程相关功能

  1. 添加课程信息到课程信息文件中,课程信息包括:课程编号(couId),
    课程名称(couName),课程人数(stuNum),选课人数(curStuNum),平均分(aveScore)
  2. 删除课程信息
  3. 改变课程信息

选课相关功能

  1. 选课,即添加学生到课程,将学生添加到当前课程的选课信息表中,需要保证:

    1. 学生已经添加到学生信息表中;
    2. 添加到已有的课程中,即课程信息表中有当前课程;
    3. 当前课程还可选,即已选课的人数未超过课程的最大人数。
      此时每个课程都对应一张选课信息文件,存储当前课程的选课情况。
      选课信息包括:学生编号(stuId)、成绩(stuScore)默认为0/空
  2. 退选,即将学生从课程中删除。
  3. 成绩录入
  4. 查看选课信息

实现目标

实现学生课程信息管理系统,可以在命令行进行上述功能的操作,最终的信息保存在相应的文件当中。

题解

怕是ACM打多了啥作业都说题解了。。

整体架构

首先我们需要思考程序的主题架构如何。也就类似作文提纲一样的东西。我们需要提炼业务需求,将程序划分成几个模块然后分别实现。这样不仅能使我们的思路清晰,还方便了我们的实现(因为逻辑混在一起一旦出错很难调试,而且后期如果要扩展也会造成困难,一堆绳子缠在一起自然难解开,我们需要先捋顺绳子)。

通过读题,我们发现题目给出了3大部分,共11个要求。这些要求基本都是增删改查系列数据库的经典操作(当然这里不给用数据库了。。)。我们对于每一个要求,实现一个具体的函数表达相关的功能。比如建立函数addStudent表示添加学生信息操作的功能等等。你可以参考下面的程序看到会有哪些函数。

此外,我们注意到我们还需要将信息存储在文件中,因此很自然地想到我们实际上还有两个需求,分别是从文件中读取信息和将信息存储在文件中。因此最后我们有13个函数,表达我们的业务逻辑(这里的业务逻辑我认为是和数据打交道的意思,这13个函数都是在操作维护学生课程信息,也就是一堆数据)。

然后我们还有绘制界面的函数,一共有4个菜单,分别是主菜单、学生相关功能菜单、课程相关功能菜单和选课相关功能菜单,每个菜单一个函数去绘制界面。比如主菜单应该有4个选项,分别是进入学生菜单、进入课程菜单、进入选课菜单和退出程序。这里要注意我们必须要有退出程序的菜单,以及子菜单必须有返回上一级的选项,作为菜单导航。否则进了程序不能正常退出(按Ctrl-C可以强制结束程序,但我们需要保存信息到文件中,强制退出会导致不保存信息,即丢失信息),进了子菜单不能返回上一级菜单就太滑稽了。

最后我们的主程序,即main函数应该做的事情就是加载数据、控制菜单逻辑和保存数据。要显示那个菜单显然不是菜单自己决定的,我们需要有一个“菜单管理器”去管理菜单,就像文件管理器一样。

部分实现

具体实现细节请参考代码。

main函数和菜单函数

我们在整体架构中已经提了主程序的实现方法,这里我们继续讲。我们给每个菜单标号,这里我们假定主菜单标号为0,学生菜单标号为1,课程菜单标号为2,选课菜单标号为3。如果我们在main函数里设置一个变量loc表示当前显示的菜单标号,然后通过标号调用对应菜单的函数绘制就可以了。注意我们绘制菜单的时候需要清屏(清屏的方法是system("cls"),当然仅适用于Windows操作系统,表示调用控制台的cls命令,这个命令的功能就是清屏)。

也就是说我们main函数实现的代码大概长这样:

loadFrom(FILE); // 加载文件信息
while (1) {
    system("cls"); // 清屏
    switch (loc) {
    case 0: printRootMenu(&loc); break;
    case 1: printSecondaryMenuForStudent(&loc); break;
    case 2: printSecondaryMenuForCourse(&loc); break;
    case 3: printSecondaryMenuForElection(&loc); break;
    case 4: return 0;
}
saveTo(FILE); // 保存文件信息

我们还需要一个特别的标号4表示退出程序。这里我的实现思路是每个菜单函数里先绘制界面,也就是不断地printf,然后scanf读入我们的选项,即选了菜单的哪一项,再判断对应的操作,比如选了添加学生信息就调用addStudent函数开始添加学生信息。最后如果选到了返回上一级菜单,也就是说loc要变了(因为loc表示当前菜单的编号,返回上一级菜单,编号自然不一样),所以我们允许每个菜单函数修改loc,比如loc=0就可以表示回到根菜单,那我们设置loc=4就表示退出程序,一个道理。当然有大佬会说你这么写很不好,确实,菜单是树形结构,如果遇到多级菜单,我们就需要记录每个菜单的父菜单是哪一个,然后返回上一级菜单就将loc=parent[loc]即可。

最后文件的管理我们通过fopen函数获取一个FILE*指针,然后传给loadFrom函数和saveTo函数使用。记得用完后调用fclose关闭文件。

业务逻辑

到了处理业务的时候了。我们这里着重讲学生信息的增删,分别对应addStudentremoveStudent

结构体

首先我们需要Student结构体描述一个学生的信息:

struct Student {
    int id;
    char name[20];
    int sex;
};

分别表示学号、名字和性别。课程信息和选课信息的结构体请参考程序。

addStudent

提示用户输入

显然我们需要添加学生的话,就需要用户输入学生id、名字和性别。
所以我们最开始需要提示用户要输入什么,所以:

printf("Please enter student id first, name second and sex later with 0 meaning male, 1 meaning female.\n");

然后要求用户连着输入id、name和sex。当然我们可以这样:

printf("Please enter student id: "); scanf("%d", &student.id);
printf("Please enter student name: "); scanf("%s", student.name);
printf("Please enter student sex with 0 male and 1 female: "); scanf("%d", &student.sex);

输入性别这方面,我们可以要求用户输入malefemale,不过这可能带来更大的代码量,而且用户可能输入错误,因此我们只允许用户输入01表示男和女。然后对于用户的输入,我们一定要注意验证用户输入的合法性,我们不能保证用户输入的东西都是正确的,用户可以在输入性别的时候输入不是0和1的数字比如2,更可以是字符串。对于用户输入了错误数字的情况,我们读取了sex以后要判断如果sex<0||sex>1直接提示用户输入错误,然后不再添加该学生信息(因为信息是错误的,我们要保证sex是正确的数值)。如果用户输入的不是数字怎么办?这时候我们就需要利用scanf返回值了,scanf返回值表示输入成功的参数个数,比如scanf("%d", &sex),如果用户输入的是字符串,那么scanf返回值就为0,否则为1表示成功输入了一个参数sex,当然还有返回值-1表示文件结束不可以再读入东西了。
因此光用户输入这里我们需要注意的就很多。

数据操作

如果我们使用数组存放学生信息,比如Student stu[1000];这样,那么很简单,stu[++stuCount]=s即可(其中Student s为新学生信息)。

不过我这里使用了链表,大概就是pushFront(&head, &count, &s, sizeof(s))这样。链表的实现细节后面再讲。

removeStudent

删除学生信息,首先我们需要用户输入我们要删除学生的名字,然后我们找到这个学生删除就可以了。也就是说我们先提示用户输入学生名,然后我们再输入字符串。然后还有用户输入的学生不存在的情况,我们要判断一下,并提示学生不存在。

数据维护

我们的数据维护有两种方式,一个是数组存储,一个是链表存储。
数组存储的好处是实现简单,但是如果数组长度不够的时候需要调用realloc函数重新申请内存,当然这消耗的时间其实也不会很多。链表存储的好处是删除快而且自由,但是好像我还没有找到好的方法抽象链表的API。。

链表结构

数组实现没啥好说的,我们这里就讲讲链表吧。
首先我们需要一个能存储任意类型的链表。这需要所谓的“泛型指针”的void*。我们定义链表节点的结构体为

struct Node {
    void *data;
    Node *next, *prev; // 我的代码是双向链表(虽然代码中不需要双向)
};

链表是啥。。建议另外上网查了。如果说数组在内存中的存储是连续的(这样我们访问数组元素只要再内存中直接定位就可以了),那么链表就是不连续存储的(这样我们就需要一个next域表示下一个节点的内存位置)。

插入节点

那么向链表头插入元素的代码是:

void pushFront(Node **head, int *count, const void *new_data, size_t size) {
    ++*count; // 元素个数加一
    Node *newNode = (Node *)malloc(sizeof(Node));
    newNode->data = malloc(size); // 复制新数据到新节点里,这么做的好处是我们可以保证无论new_data是不是临时变量,函数都可以正常工作
    memcpy(newNode->data, new_data, size); // 可以试一试将这两行改成newNode->data = new_data; 试试程序会发生什么。
    newNode->next = *head; // 维护链表链接
    newNode->prev = NULL;
    if (*head != NULL) (*head)->prev = newNode;
    *head = newNode;
}

因为我们表示任意类型只有void*这个类型,还是个指针(当然实际上也只能是指针),因此我们在使用的时候必须适应指针,需要另外申请空间存放数据。也就是malloc(size)的作用。至于为什么不是直接用new_data,是因为我想简化程序实现,如果我们直接用new_data,那么我们在调用pushFront时传入的new_data就不能是不持久的变量的地址(比如函数的局部变量在退出函数后就从内存中“消失”了,也就是不持久,这样如果我们持有一个不持久变量的指针,一旦变量从内存中消失了后我们再访问这块内存空间会发生什么呢?当然是访问了无效内存程序崩溃了)。然后我们在pushFront传参就必须调用malloc函数新建空间,这样我们就不用重复写malloc了。而且如果我们读入Student失败的时候,还需要free删除,因为不会加到链表里。

查找节点

// 在链表node中查找element元素,cmp为比较函数
Node *find(Node *node, const void *element, int(*cmp)(const void *, const void *)) {
    if (node == NULL) return NULL;
    if (cmp(node->data, element) == 0) return node; // 比较当前节点的数据和element是不是一样的,如果是返回当前节点。
    else return find(node->next, element, cmp); // 否则向后继续寻找
}

首先我们传入的要查找的东西只能是void*类型,因为我们写find的时候不知道链表节点存储的数据类型,当然如果你特化find也可以,不过我们泛化find可以重复利用这些代码更好。
既然是void*,我们就不知道数据怎么比较,自然我们就需要另外给一个比较函数cmp,类型是两个void*参数返回int的函数(看不懂的可以找一下函数指针是什么)。我们规定cmp函数返回0的时候表示两个元素是相等的。那么一个简单的递归就解决问题了。关于递归的理解可网上多找一下,可以理解为问题的不断转化,但问题性质解法都不变,比如这个find函数就是我们查一个大链表可以转化为查第一个节点和剩下节点组成的链表。实际上链表就是递归定义的。还可以了解一下函数式语言,比如Haskell和Scheme,深入学习一下递归是什么。

删除节点

// 从链表中删除元素,head表示链表头,count表示链表元素个数,node表示要删除的节点
void delete_node(Node **head, int *count, Node *node) {
    if (*head == NULL) return; // 如果node存在的话head就应该不会为NULL(因为链表不为空,因此至少有一个节点,必存在首节点head)
    else if (*head == node) { // 如果我们在删除链表的首节点,我们就要维护head了,这也是head为什么是二重指针的原因。参数是二重指针我们就可以修改head存储的地址了。
        *head = (*head)->next; // head不要了
        if (*head != NULL) // 指针操作的时候要特别注意NULL的情况,如果链表只有一个元素的时候即为此情况,即要删除的唯一的节点没有next。
            (*head)->prev = NULL; // 显然首节点是没有上一个节点的
        free(node->data); // 及时释放内存空间
        free(node);
        --*count; // 链表元素少了一个
    }
    else { // 如果删除的不是首节点
        node->prev->next = node->next; // 那么node就一定有上一个节点(因为node不是首节点,只有首节点没有上一个节点)
        if (node->next != NULL) // 如果node不是最后一个节点
            node->next->prev = node->prev; // 那么node下一个节点也要维护
        free(node->data); // 及时释放内存空间
        free(node);
        --*count; // 链表元素少了一个
    }
}

删除节点,需要特别判断一下删除的是不是头指针,然后维护一下nextprev域,即链表的链,保证正确。具体参考注释(这里的注释和下面程序的注释不太一样,还请看一下)。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define STR_LEN 128

// 链表结构体
typedef struct Node {
    void *data;
    struct Node *next, *prev;
} Node;

typedef struct Student {
    int id; // 学生学号
    char name[STR_LEN]; // 学生名字
    int sex; // 0表示男性,1表示女性
} Student;

// 比较函数,只接受Student*类型的参数,比较两个Student的名字,0表示相同。
int stuCompare(const void *a, const void *b) {
    return strcmp(((Student *)a)->name, ((Student *)b)->name);
}

// 比较函数,只接受Student*类型的参数,比较两个Student的学号,0表示相同。
int stuCompareId(const void *a, const void *b) {
    return ((Student *)a)->id - ((Student *)b)->id;
}

// 课程选课学生信息
typedef struct CourseStudent {
    int id, score; // 学生的学号和这个学生在该课程中的分数。
} CourseStudent;

// 比较函数,只接受CourseStudent*参数,比较两个的学号,0表示一样。
int curStuCompare(const void *a, const void *b) {
    return ((CourseStudent *) a)->id - ((CourseStudent *)b)->id;
}

// 课程信息结构体
typedef struct Course {
    int id, stuNum, num; // 课程ID,已有多少个学生选课,最多多少个学生选课
    double aveScore; // 课程平均分
    char name[STR_LEN]; // 课程名称
    struct Node *curStuHead; // 有哪些学生选了这个课,一个链表
} Course;

// 比较函数,比较两个课程的名称,0表示一样,即两个Course*是同一个课程。
int curCompare(const void *a, const void *b) {
    return strcmp(((Course *)a)->name, ((Course *)b)->name);
}

int stuCount = 0, curCount = 0, curId;
Node *stuHead = NULL, *curHead = NULL; // 存储学生信息的链表和存储课程信息的链表

// 向链表最前端添加节点,head表示链表头,count表示链表元素个数,new_data表示新节点的数据信息,size表示数据信息在内存中存储需要的字节数,方便memcpy和malloc的时候使用内存
void pushFront(Node **head, int *count, const void *new_data, size_t size) {
    ++*count; // 元素个数加一
    Node *newNode = (Node *)malloc(sizeof(Node));
    newNode->data = malloc(size); // 复制新数据到新节点里,这么做的好处是我们可以保证无论new_data是不是临时变量,函数都可以正常工作
    memcpy(newNode->data, new_data, size); // 可以试一试将这两行改成newNode->data = new_data; 试试程序会发生什么。
    newNode->next = *head; // 维护链表链接
    newNode->prev = NULL;
    if (*head != NULL) (*head)->prev = newNode;
    *head = newNode;
}

// 在链表node中查找element元素,cmp为比较函数
Node *find(Node *node, const void *element, int(*cmp)(const void *, const void *)) {
    if (node == NULL) return NULL;
    if (cmp(node->data, element) == 0) return node; // 比较当前节点的数据和element是不是一样的,如果是返回当前节点。
    else return find(node->next, element, cmp); // 否则向后继续寻找
}

// 从链表中删除元素,head表示链表头,count表示链表元素个数,node表示要删除的节点
void delete_node(Node **head, int *count, Node *node) {
    if (*head == NULL) return;
    else if (*head == node) {
        *head = (*head)->next;
        if (*head != NULL)
            (*head)->prev = NULL;
        free(node->data);
        free(node);
        --*count;
    }
    else {
        node->prev->next = node->next;
        if (node->next != NULL)
            node->next->prev = node->prev;
        free(node->data);
        free(node);
        --*count;
    }
}

// 保存学生信息链表到文件中
void saveStudent(FILE *pFile, Node *stuHead, int stuCount) {
    fprintf(pFile, "%d\n", stuCount);
    for (Node *i = stuHead; i != NULL; i = i->next) {
        Student *s = (Student *)i->data;
        fprintf(pFile, "%d %s %d\n", s->id, s->name, s->sex);
    }
}

// 从文件中加载学生信息链表
void loadStudent(FILE *pFile, Node **stuHead, int *stuCount) {
    int c;

    *stuHead = NULL;
    *stuCount = 0;

    if (pFile == NULL) { // 如果第一次启动程序,文件不存在,我们不再读取信息
        return;
    }
    fscanf(pFile, "%d", &c);
    for (int i = 0; i < c; ++i) {
        Student s;
        fscanf(pFile, "%d%s%d", &s.id, s.name, &s.sex);
        pushFront(stuHead, stuCount, &s, sizeof(Student));
    }
}

// 保存学生选课信息
void saveCourseStudent(FILE *pFile, Node *curHead, int stuNum) {
    fprintf(pFile, "%d\n", stuNum);
    for (Node *i = curHead; i != NULL; i = i->next) { // 遍历链表
        CourseStudent *cs = (CourseStudent *)i->data;
        fprintf(pFile, "%d %d\n", cs->id, cs->score);
    }
}

// 加载学生选课信息
void loadCourseStudent(FILE *pFile, Node **curHead, int *stuNum) {
    int c;

    *curHead = NULL;

    fscanf(pFile, "%d", &c);
    for (int i = 0; i < c; ++i) {
        CourseStudent s;
        fscanf(pFile, "%d%d", &s.id, &s.score);
        pushFront(curHead, stuNum, &s, sizeof(CourseStudent));
    }
}

// 保存课程信息链表到文件中
void saveCourse(FILE *pFile, Node *curHead, int curId, int curCount) {
    fprintf(pFile, "%d %d\n", curId, curCount);
    for (Node *i = curHead; i != NULL; i = i->next) { // 遍历链表
        Course *c = (Course *)i->data;
        fprintf(pFile, "%d %s %d %f\n", c->id, c->name, c->num, c->aveScore);
        saveCourseStudent(pFile, c->curStuHead, c->stuNum);
    }
}

// 从文件中加载课程信息链表
void loadCourse(FILE *pFile, Node **curHead, int *curId, int *curCount) {
    int c;

    *curHead = NULL;
    *curCount = 0;

    if (pFile == NULL) { // 如果第一次启动程序,文件不存在,我们不再读取信息
        *curId = 0; // 并初始化数据
        return;
    }
    fscanf(pFile, "%d%d", curId, &c);
    for (int i = 0; i < c; ++i) {
        Course c;
        fscanf(pFile, "%d%s%d%lf", &c.id, c.name, &c.num, &c.aveScore);
        c.curStuHead = NULL; c.stuNum = 0;
        loadCourseStudent(pFile, &c.curStuHead, &c.stuNum);

        pushFront(curHead, curCount, &c, sizeof(Course));
    }
}

// 业务逻辑:保存数据到文件中
void saveTo(FILE *pFile) {
    saveStudent(pFile, stuHead, stuCount);
    saveCourse(pFile, curHead, curId, curCount);
}

// 业务逻辑:从文件中加载数据
void loadFrom(FILE *pFile) {
    stuHead = NULL; curHead = NULL;
    loadStudent(pFile, &stuHead, &stuCount);
    loadCourse(pFile, &curHead, &curId, &curCount);
}

// 业务逻辑:添加学生信息
void addStudent() {
    Student s;
    puts("Please enter student id first, name second and sex later with 0 meaning male, 1 meaning female.");
    scanf("%d%s%d", &s.id, s.name, &s.sex);
    if (s.sex < 0 || s.sex > 1) // 检查输入数据的合法性
        puts("Sex wrong, only 0(male) and 1(female) allowed.");
    else
        pushFront(&stuHead, &stuCount, &s, sizeof(Student));
}

// 业务逻辑:修改学生信息
void modifyStudent() {
    Student s;
    char name[STR_LEN];
    int sex;
    puts("Please enter the name of student to modify.");
    scanf("%s", s.name);
    Node *node = find(stuHead, &s, stuCompare);
    if (node == NULL) { // 找不到学生,报错
        printf("Student %s not found.\n", s.name);
    }
    else {
        Student *pStu = (Student *)node->data;
        puts("Please enter new name first, new sex later with 0 meaning male, 1 meaning female.");
        scanf("%s%d", name, &sex);
        if (sex < 0 || sex > 1) // 检查输入数据的合法性
            puts("Sex wrong, only 0(male) and 1(female) allowed.");
        else {
            memcpy(pStu->name, name, sizeof(name));
            pStu->sex = sex;
        }
    }
}

// 业务逻辑:删除学生信息
void removeStudent() {
    Student s;
    printf("Please enter the name of student to be removed.\n");
    scanf("%s", s.name);
    Node *node = find(stuHead, &s, stuCompare);
    if (node == NULL) { // 如果没找到学生,报错
        printf("Student %s not found.\n", s.name);
    }
    else {
        delete_node(&stuHead, &stuCount, node);
    }
}

// 业务逻辑:查询学生信息
void showStudent() {
    Student s;
    printf("Please enter the name of student to be searched.\n");
    scanf("%s", s.name);
    Node *node = find(stuHead, &s, stuCompare);
    if (node == NULL) { // 如果没找到学生,报错
        printf("Student %s not found.\n", s.name);
    }
    else {
        Student *pStu = (Student *)node->data;
        printf("Student %s, id: %d, sex %s.\n", pStu->name, pStu->id, pStu->sex ? "female" : "male");
    }
}

// 业务逻辑:查询所有学生的信息
void listStudent() {
    printf("Student name | sex | elected courses\n");
    for (Node *i = stuHead; i != NULL; i = i->next) {
        Student *pStu = (Student *)i->data;
        printf("%s | %s | ", pStu->name, pStu->sex ? "female" : "male");

        for (Node *j = curHead; j != NULL; j = j->next) {
            Course *pCur = (Course *)j->data;
            CourseStudent cs = { pStu->id, 0 };
            if (find(pCur->curStuHead, &cs, curStuCompare) != NULL)
                printf("%s ", pCur->name);
        }
        putchar('\n');
    }
}

// 业务逻辑:添加课程信息
void addCourse() {
    Course c;
    printf("Please enter course name first, max student number second.\n");
    if (scanf("%s%d", c.name, &c.num) != 2) // 如果输入失败,取消这次操作
        return;
    if (c.num <= 0) { // 检查输入数据的合法性
        puts("Max student number wrong, only positive number allowed");
        return;
    }
    c.id = ++curId;
    c.stuNum = 0;
    c.aveScore = 0;
    c.curStuHead = NULL;
    pushFront(&curHead, &curCount, &c, sizeof(Course));
}

// 业务逻辑:删除课程信息
void removeCourse() {
    Course c;
    printf("Please enter the name of course to be removed.\n");
    if (scanf("%s", c.name) != 1) // 如果输入失败,取消这次操作
        return;
    Node *node = find(curHead, &c, curCompare);
    if (node == NULL) {
        printf("Course %s not found.\n", c.name);
    }
    else {
        delete_node(&curHead, &curCount, node);
    }
}

// 业务逻辑:修改课程信息
void modifyCourse() {
    Course c;
    printf("Please enter the name of course to modify.\n");
    scanf("%s", c.name);
    Node *node = find(curHead, &c, curCompare);
    if (node == NULL) {
        printf("Course %s not found.\n", c.name);
    }
    else {
        printf("Please enter new name and new max student number.\n");
        Course *pCur = (Course *)node->data;
        scanf("%s%d", pCur->name, &pCur->num);
    }
}

// 业务逻辑:查询课程信息
void showCourse() {
    Course c, *pCur;
    puts("Please enter course's name.");
    scanf("%s", c.name);
    Node *course = find(curHead, &c, curCompare);
    if (course == NULL) {
        printf("Course %s not found.\n", c.name);
        return;
    }
    pCur = (Course *)course->data;

    printf("Average score: %f\n", pCur->aveScore);
    puts("Student name | score");

    for (Node *i = pCur->curStuHead; i != NULL; i = i->next) {
        CourseStudent *cs = (CourseStudent *)i->data;
        Student s, *pStu;
        s.id = cs->id;
        Node *node = find(stuHead, &s, stuCompareId);
        pStu = (Student *)node->data;
        printf("%s | %d\n", pStu->name, cs->score);
    }
}

// 根据学生和课程信息查询学生是否已选该课程,输出选课信息到pCurStu中,返回0表示学生或课程不存在(而不是学生未选该课程,由*pCurStu==NULL表示)
int findCourseStudent(Student **pStu, Course **pCur, Node **pCurStu) {
    Student s;
    Course c;
    scanf("%s%s", s.name, c.name);
    Node *pNodeStu = find(stuHead, &s, stuCompare);
    Node *pNodeCur = find(curHead, &c, curCompare);
    if (pNodeStu == NULL) { // 学生不存在
        printf("Student %s not found.\n", s.name);
        return 0;
    }

    if (pNodeCur == NULL) { // 课程不存在
        printf("Course %s not found.\n", c.name);
        return 0;
    }

    *pStu = (Student *) pNodeStu->data;
    *pCur = (Course *) pNodeCur->data;

    CourseStudent cs = { (*pStu)->id, 0 };
    *pCurStu = find((*pCur)->curStuHead, &cs, curStuCompare);

    return 1;
}

// 业务逻辑:选课
void electCourse() {
    Student *pStu;
    Course *pCur;
    Node *pNodeStuCur;
    printf("Please enter student's name and course's name to bind.\n");

    if (!findCourseStudent(&pStu, &pCur, &pNodeStuCur))
        return;

    if (pCur->stuNum >= pCur->num) { // 课程已满人,不可以再选该课程
        printf("The number of electives has reached the limit.\n");
        return;
    }

    if (pNodeStuCur != NULL) { // 学生已选择该课程,不可以再选
        printf("Student %s has elected Course %s.\n", pStu->name, pCur->name);
        return;
    }
    CourseStudent cs = { pStu->id, 0 };
    pCur->aveScore = (pCur->aveScore * pCur->stuNum + cs.score) / (pCur->stuNum + 1); // 更新平均分
    pushFront(&pCur->curStuHead, &pCur->stuNum, &cs, sizeof(CourseStudent));
}

// 业务逻辑:退选
void unelectCourse() {
    Student *pStu;
    Course *pCur;
    Node *pNodeCurStu;
    printf("Please enter student's name and course's name to bind.\n");

    if (!findCourseStudent(&pStu, &pCur, &pNodeCurStu)) // 没有这个学生或课程
        return;

    if (pNodeCurStu == NULL) { // 学生未选该课程
        printf("Student %s hasn't elected Course %s.\n", pStu->name, pCur->name);
        return;
    }

    CourseStudent *cs = (CourseStudent *)pNodeCurStu->data;
    if (pCur->stuNum == 1) pCur->aveScore = 0;
    else pCur->aveScore = (pCur->aveScore * pCur->stuNum - cs->score) / (pCur->stuNum - 1);
    delete_node(&pCur->curStuHead, &pCur->stuNum, pNodeCurStu);
}

// 业务逻辑:更新学生课程成绩
void updateStudentScore() {
    int score;
    Student *pStu;
    Course *pCur;
    Node *pNodeCurStu;
    puts("Please enter new score, student's name and course's name.");
    scanf("%d", &score);

    if (!findCourseStudent(&pStu, &pCur, &pNodeCurStu))
        return;

    if (pNodeCurStu == NULL) {
        printf("Student %s hasn't elected Course %s.\n", pStu->name, pCur->name);
        return;
    }

    CourseStudent *pCS = (CourseStudent *)pNodeCurStu->data;
    pCur->aveScore = (pCur->aveScore * pCur->stuNum - pCS->score + score) / pCur->stuNum;
    pCS->score = score;
}

// 界面逻辑:主菜单
void printRootMenu(int *loc) {
    int op;

    puts("Enter 1 to manage students.");
    puts("Enter 2 to manage courses. ");
    puts("Enter 3 to manage election.");
    puts("Enter 4 to exit program.");

    scanf("%d", &op);
    if (op < 1 || op > 4) puts("Unrecognized operation.");
    else {
        *loc = op;
    }
}

// 界面逻辑:学生信息管理二级菜单
void printSecondaryMenuForStudent(int *loc) {
    int op;

    puts("Enter 1 to add a new student.");
    puts("Enter 2 to remove a student. ");
    puts("Enter 3 to change a student's profile.");
    puts("Enter 4 to view student's profile.");
    puts("Enter 5 to list student.");
    puts("Enter 6 to go back to previous menu.");

    scanf("%d", &op);
    switch (op) {
    case 1: addStudent(); system("pause"); break;
    case 2: removeStudent(); system("pause"); break;
    case 3: modifyStudent(); system("pause"); break;
    case 4: showStudent(); system("pause"); break;
    case 5: listStudent(); system("pause"); break;
    case 6: *loc = 0; break;
    default: puts("Unrecognized operation."); break;
    }
}

// 界面逻辑:课程信息管理二级菜单
void printSecondaryMenuForCourse(int *loc) {
    int op;

    puts("Enter 1 to add a new course.");
    puts("Enter 2 to remove a course. ");
    puts("Enter 3 to change a course's profile.");
    puts("Enter 4 to go back to previous menu.");

    scanf("%d", &op);
    switch (op) {
    case 1: addCourse(); system("pause"); break;
    case 2: removeCourse(); system("pause"); break;
    case 3: modifyCourse(); system("pause"); break;
    case 4: *loc = 0; break;
    default: puts("Unrecognized operation."); break;
    }
}

// 界面逻辑:选课信息管理二级菜单
void printSecondaryMenuForElection(int *loc) {
    int op;

    puts("Enter 1 to elect a course.");
    puts("Enter 2 to unelect a course. ");
    puts("Enter 3 to update a student's score.");
    puts("Enter 4 to view course's election.");
    puts("Enter 5 to go back to previous menu.");

    scanf("%d", &op);
    switch (op) {
    case 1: electCourse(); system("pause"); break;
    case 2: unelectCourse(); system("pause"); break;
    case 3: updateStudentScore(); system("pause"); break;
    case 4: showCourse(); system("pause"); break;
    case 5: *loc = 0; break;
    default: puts("Unrecognized operation."); break;
    }
}

// 主程序
int main() {
    int loc = 0;
    FILE *p = fopen("settings.txt", "r");
    loadFrom(p);
    fclose(p);

    while (1) {
        system("cls");
        puts("Welcome to student information management system.");

        if (loc == 0) {
            printRootMenu(&loc);
            if (loc == 4)
                break;
        }
        else if (loc == 1) {
            printSecondaryMenuForStudent(&loc);
        }
        else if (loc == 2) {
            printSecondaryMenuForCourse(&loc);
        }
        else if (loc == 3) {
            printSecondaryMenuForElection(&loc);
        }
    }

    p = fopen("settings.txt", "w");
    saveTo(p);
    fclose(p);

    return 0;
}

转载自原文链接, 如需删除请联系管理员。

原文链接:实现简单的学生选课信息管理系统,转载请注明来源!

0