實現簡單的學生選課信息管理系統

中山大學軟件工程程序設計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;
}
發佈了486 篇原創文章 · 獲贊 47 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章