C语言课程设计:单项选择题标准化考试系统 | JorbanS

导入时图片加载异常,故本文图例和图片均已隐去,只保留了功能结构图。

程序设计环境:Visual Studio Code, Sublime 4。

一、课程设计任务

1.1课题背景

本项目旨在开发一个单项选择题标准化考试系统,能够方便地管理试题库,抽取试题,进行答题和自动判卷。该系统的实现需要借助计算机编程技术和文件操作技术,以实现试题库的管理和试题的抽取、答题和判卷等功能。考生可以通过该系统进行模拟考试,系统能够根据考生的答案和标准答案的对比,自动判卷并给出成绩,为考生提供便利和准确的考试评估。

该系统的开发旨在提高考试管理的效率和准确性,同时为考生提供更加便捷和准确的考试体验。同时,该系统也可以应用于各种类型的考试,如教育机构的入学考试、职业资格考试等,具有广泛的应用前景和市场价值。

本系统完全独立开发,力求使系统功能简洁明了,且功能齐全且易于操作。

1.2目的背景和意义

开发这个项目的主要目的是提供一个方便、高效、可靠的单项选择题标准化考试系统,以解决传统考试方式中存在的一些问题,如试题管理不便、考试效率低下、判卷难度大等。

提高考试管理的效率和准确性。传统的考试方式中,试题的管理、抽取、答题和判卷等环节都需要人工操作,存在较大的误差和不确定性。而该系统能够实现试题库的自动管理和试题的自动抽取、判卷等功能,大大提高了考试管理的效率和准确性。

提高考试的公平性和准确性。通过该系统进行考试,每个考生所抽取的试题都是随机的,试题的难度和内容分布也是随机的,因此可以有效避免试题的不公平性和偏差性。同时,系统能够自动判卷,减少了人工判卷所产生的误差和主观性,提高了考试的准确性和公正性。

提高考生的答题效率和体验。传统考试方式中,考生需要在试卷上标出答案,容易出现漏填、误填等情况,同时也需要手动计算分数和核对答案,效率低下。而该系统能够自动记录考生答案并自动判卷,大大提高了答题效率和准确性,同时也提供了更加便捷和舒适的考试体验。

具有广泛的应用前景和市场价值。考试是社会各行业和领域中必不可少的一环,如教育、招聘、职业资格认证等。该系统具有广泛的应用前景和市场价值,可以为各种类型的考试提供方便、快捷、准确的考试管理和评估服务,为社会提供更加高效、公正、科学的考试方式,对于提高社会整体素质和人才培养也具有积极的推动作用。

1.3项目开发的目标

      集成题库管理系统和单项选择题答题,采用计算机对学生答题进行管理,进一步提高办学效益和现代化水平。帮助广大教师提高工作效率,实现学生答题工作流程的系统化、规范化和自动化。

二、总体设计方案

2.1单项选择题标准化考试系统的功能

如图1所示,打印成绩信息,打印题库信息,增、删、改题目,学生答题及判卷,学生和老师权限组分离,读写成绩信息和题库信息。

C语言课程设计:单项选择题标准化考试系统 | JorbanS_第1张图片

2.2模块设

2.2.1主模块

功能:显示欢迎语,建立学生成绩和题库的链表头结点,并文件中读取信息,建立链表,同时返回已答题学生的数量和题目数量,便于后续操作。

2.2.2字符转换模块

             功能:统一输入的选项字符ABCDabcd为大写的ABCD

2.2.3选择随机题目模块

             功能:挑选随机不重复的题目,返回题目指针。

2.2.4清空题目状态信息模块

             功能:初始化所有题目状态为未使用状态。

2.2.5读取题目模块

             功能:从文件中读取所有题目,返回题库中题目数量。

2.2.6读取学生成绩模块

             功能:从文件中读取所有学生成绩信息,返回已答题学生数量。

2.2.7保存题目模块

             功能:读取题库链表所有题目,保存到文件中。

2.2.8保存学生成绩模块

             功能:读取学生链表中所有学生信息,保存到文件中。

2.2.9打印学生成绩模块

             功能:打印所有学生的信息和对应的成绩、题目数量。

2.2.10打印题库题目模块

             功能:打印题库中的所有题目。

2.2.11题目删除模块

             功能:删除不需要的题目,回收内存。

2.2.12题目修改模块

             功能:提供待修改题目信息,便于对比修改题目。

2.2.13增加题目模块

             功能:向题库中增加新题。

2.2.14教师管理模块

             功能:题库打印,题库管理,学生成绩查看。

2.2.15教师登录模块

             功能:验证管理员资格。

2.2.16学生登录答题模块

             功能:学生登录,抽取题目,答题,判题。

2.3所用结构体格式

typedef struct _Student { // 学生成绩链表

        char name[20], idx[20]; // 存储姓名、学号

        int grade, numQue; // 存储分数、答题数量

        _Student* next; // next指针

} Stu;

typedef struct _Problems { // 问题链表

        int idx, state; // 存储学号、状态(是否被使用过)

        char q[50], op[4][25], ans; // 存储问题、四个选项、答案

        _Problems* next; // next指针

} Pro;

2.4所用到的头文件

#include

#include

#include

#include

#include

2.5 所用到的宏定义

#define cls system("cls") // 便于之后的清屏操作

#define endl puts("") // 便于之后的换行操作

#define title(x) printf("============%s============\n", x) // 便于之后的标题操作

三、详细设计/具体实现

程序开发分为三个部分:界面UI、功能主体(链表存学生信息和题库)和主函数。

3.1字符转换

所用函数:

char convert(char c)

实现功能的方法分析:

如图2所示,将输入的一个字符统一转换为大写字母,用if语句判断,如果该字符是小写字母,则将其转换为对应的大写字母,如果该字符不是小写字母,则保持原样变。

3.2随机抽取问题

所用函数:

 Pro* getRandomProblem(Pro* proHead, int countProblems)

实现功能的方法分析:

如图3所示,从一组问题中随机抽取一个尚未回答过的问题。使用srand(time(NULL));语句来初始化随机数种子,以确保每次随机的结果都不相同,其中结构体的state变量表示是否被使用的状态。使用rand() % countProblems + 1这个语句来生成一个随机数,该随机数的范围是1 ~ countProblems,其中countProblems表示问题的总数。使用for循环遍历问题链表,寻找与随机数所对应的问题。循环中的变量point表示问题链表中的当前节点。如果找到了与随机数所对应的问题,再进一步判断该问题是否已经被回答过。如果该问题已经被回答过,则跳出内层循环,重新生成随机数;否则,返回该问题的指针。如果没有找到与随机数所对应的问题,则继续生成随机数,直到找到为止。

3.3清空题目状态

所用函数:

void clearState(Pro* proHead, int countProblems)

实现功能的方法分析:

如图3所示,清空所有问题的状态,使用for循环遍历问题链表,从链表的第一个节点开始遍历。循环中的变量point表示问题链表中的当前节点。对于每个节点,将其状态标志state设置为0,表示该问题尚未被回答过。循环继续遍历下一个节点,直到遍历完整个链表为止。

3.4从文件中读取题库

所用函数:

 int readProblems(Pro* proHead)

实现功能的方法分析:

如图5所示,从文件中读取一组问题,将其存储到链表中,并返回问题的总数。打开test.txt文件,使用fopen("test.txt", "r")语句打开一个只读文件流。从文件中读取问题总数,使用fscanf(fpReadProblems, "%s", cnt)和for循环将字符串cnt中的数据转换为整数类型。依次读取每个问题,并将其存储到链表中,使用malloc()动态分配内存空间。读取问题的题目、选项和答案,使用fscanf()语句读取。给问题赋予一个唯一的索引值idx。将问题插入到链表中。关闭文件流,使用fclose()语句关闭文件,返回问题的总数。

3.5从文件中读取学生成绩

所用函数:

 int readStudents(Stu* stuHead)

实现功能的方法分析:

如图6所示,打开stu.txt文件,使用fopen("stu.txt", "r")语句打开一个只读文件流。从文件中读取学生总数,使用fscanf(fpReadStudents, "%s", cnt)和for循环将字符串cnt转换为整数类型。忽略文件中的表头,使用fscanf(fpReadStudents, "%*s%*s%*s%*s")语句跳过表头。依次读取每个学生的成绩,并将其存储到链表中,使用malloc()动态分配内存空间。读取学生的姓名、学号、成绩和回答问题的数量,使用fscanf()语句读取。将学生插入到链表中。关闭文件流,使用fclose()语句关闭文件。返回学生的总数。

3.6将链表中的题目信息保存到文件中

所用函数:

void saveProblems(Pro* proHead, int countProblems)

实现功能的方法分析:

如图7所示,打开一个名为test.txt的文件,并指定以只写方式打开,使用fopen("test.txt", "w")语句。写入问题总数信息,使用fprintf(fpWriteProblems, "当前有题目:%d个\n\n", countProblems)语句将问题总数写入文件中。遍历问题链表,使用for循环遍历链表中的每个问题,对于每个问题,使用fprintf()语句将其题目、选项和答案写入文件中。关闭文件,使用fclose(fpWriteProblems)语句关闭文件。将链表头指针的next属性设置为NULL,表示链表已经被清空。

3.7将链表中的学生信息保存到文件中

所用函数:

 void saveStudents(Stu* stuHead, int countStudents)

实现功能的方法分析:

如图8所示,打开一个名为stu.txt的文件,并指定以只写方式打开,使用fopen("stu.txt", "w")语句。写入学生总数信息,使用fprintf(fpSaveStudents, "总计有%d个学生已答题\n\n", countStudents)语句将学生总数写入文件中。写入学生成绩表头,使用fprintf(fpSaveStudents, "姓名\t学号\t成绩\t题目\n")语句写入表头。遍历学生成绩链表,使用for循环遍历链表中的每个学生,对于每个学生,使用fprintf()语句将其姓名、学号、成绩和回答的问题数量写入文件中。将链表头指针的next属性设置为NULL,表示链表已经被清空。关闭文件,使用fclose(fpSaveStudents)语句关闭文件。

3.8 输出所有题目

所用函数:

 void outputProblems(Pro* proHead, int countProblems)

实现功能的方法分析:

如图9所示,清屏,使用cls语句清屏。输出表头,使用title(" 题库题目 ")和endl语句输出表头。遍历问题链表,使用for循环遍历链表中的每个问题,对于每个问题,使用printf()语句将其索引、题目、选项和答案输出到屏幕上。输出问题总数,使用printf("当前有题目:%d个\n\n", countProblems)语句输出问题总数。输出表尾,使用title(" 题库题目 ")语句输出表尾。

3.9输出所有学生成绩

所用函数:

 void outputStudents(Stu* stuHead, int countStudents)

实现功能的方法分析:

如图10所示,清屏,使用cls语句清屏。输出表头,使用title(" 学生成绩表 ")和endl语句输出表头,并通过puts()语句输出表格的列名。遍历学生成绩链表,使用for循环遍历链表中的每个学生,对于每个学生,使用printf()语句将其姓名、学号、成绩和回答的问题数量输出到屏幕上。输出学生总数,使用printf("已有%d人完成答题\n", countStudents)语句输出学生总数。输出表尾,使用title("===========")语句输出表尾。

3.10删除指定序号的题目

所用函数:

 void deleteProblem(Pro* proHead, int toBeDeleted)

实现功能的方法分析:

如图11所示,从链表头开始遍历链表,使用for循环遍历链表中的每个问题,对于每个问题,使用if语句判断其索引是否等于待删除的索引。如果找到了待删除的问题,使用last->next = point->next语句将待删除的问题从链表中删除,同时使用free()函数释放该问题所占用的内存空间。如果没有找到待删除的问题,继续遍历下一个问题。

3.11编辑指定序号的题目

所用函数:

 int editProblem(Pro* proHead, int countProblems, int problemToBeEdited)

实现功能的方法分析:

如图12所示,清屏,使用cls语句清屏。输出表头,使用title(" 变更题目 ")和endl语句输出表头,并使用puts()语句输出编辑操作的选项。读取用户输入,使用scanf()函数读取用户选择的编辑操作。如果用户选择退出,使用return countProblems语句返回问题总数。如果用户选择删除问题,使用deleteProblem()函数删除指定问题,并使用countProblems语句将问题总数减1,然后使用return countProblems语句返回问题总数。如果用户选择修改问题,使用for循环遍历链表中的每个问题,找到指定的问题。对于指定的问题,使用printf()语句输出其题目和选项,然后使用scanf()函数读取用户输入的修改信息。如果用户输入的修改信息不为next,则使用相关的字符串处理函数修改问题的题目或选项,使用strcmp()函数进行字符串比较和strcpy()函数进行字符串复制。对于指定的问题,使用printf()语句输出其答案,然后使用scanf()函数读取用户输入的修改信息。如果用户输入的修改信息不为next,则修改问题的答案。使用return countProblems语句返回问题总数。

3.12新增题目

所用函数:

 int (Pro* proHead, int countProblems)

实现功能的方法分析:

如图13所示,清屏,使用cls语句清屏。输出表头,使用title(" 新增题目 ")和endl语句输出表头。动态分配内存,使用malloc()函数动态分配一个新的问题节点addNewProblems。使用scanf()函数读取用户输入的新问题的题目。使用for循环读取用户输入的新问题的选项,并将其存储到addNewProblems->op[i]中。使用sprintf()和strcpy()函数将选项和选项的标号合并起来,然后将其存储到addNewProblems->op[i]中。使用getchar()函数读取用户输入的新问题的答案,并使用convert()函数将其转换为大写字母,然后将其存储到addNewProblems->ans中。将新问题节点插入到链表头部,使用addNewProblems->next = proHead->next和proHead->next = addNewProblems语句。将问题总数加1,使用countProblems ++语句。使用return countProblems语句返回问题总数。

3.13管理题库

所用函数:

 int manageProblems(Pro* proHead, int countProblems)

实现功能的方法分析:

如图14所示,清屏,使用cls语句清屏。输出表头,使用title(" 管理题目 ")和endl语句输出表头,并使用puts()语句输出管理操作的选项。读取用户输入,使用scanf()函数读取用户选择的管理操作。如果用户选择退出,使用return countProblems语句返回问题总数。如果用户选择更改题目,使用outputProblems()函数输出问题列表,并使用scanf()函数读取用户输入的待编辑问题的编号,然后使用editProblem()函数编辑指定问题,并使用返回的问题总数更新countProblems的值。如果用户选择新增题目,使用addProblem()函数向链表中添加新问题,并使用返回的问题总数更新countProblems的值。使用return countProblems语句返回问题总数。

3.14老师管理界面

所用函数:

 int startTeacher(Stu* stuHead, Pro* proHead, int countStudents, int countProblems)

实现功能的方法分析:

如图15所示,使用while (true)循环,直到用户输入正确的账号和密码为止。清屏,使用cls语句清屏。输出表头,使用title(" 教师登录 ")和endl语句输出表头,并使用scanf()函数读取用户输入的账号和密码。如果用户输入的账号和密码正确,使用break语句跳出循环。如果用户输入的账号和密码错误,使用cls语句清屏,输出错误提示信息,使用Sleep()函数暂停程序执行1.5秒后重新进入循环。使用while (true)循环,直到用户选择退出操作为止。清屏,使用cls语句清屏。输出表头,使用title(" 教师管理 ")和endl语句输出表头,并使用puts()语句输出管理操作的选项。读取用户输入,使用scanf()函数读取用户选择的管理操作。如果用户选择退出,使用return countProblems语句返回问题总数。如果用户选择显示所有题目,使用outputProblems()函数输出问题列表,然后使用scanf()函数读取任意字符,等待用户输入任意字符后返回操作界面。如果用户选择管理题库,使用manageProblems()函数管理题库,并使用返回的问题总数更新countProblems的值。如果用户选择学生成绩查看,使用outputStudents()函数输出学生列表,然后使用scanf()函数读取任意字符,等待用户输入任意字符后返回操作界面。使用saveProblems()函数保存题库数据,使用readProblems()函数重新读取题库数据。

3.15学生答题界面

所用函数:

 void startStudent(Stu* stuHead, Pro* proHead, int countStudents, int countProblems)

实现功能的方法分析:

如图16所示,动态分配内存,使用malloc()函数动态分配一个新的学生节点addNewStudent。清屏,使用cls语句清屏。输出表头,使用title(" 考生登录 ")和endl语句输出表头,并使用scanf()函数读取用户输入的姓名和学号。使用clearState()函数清除所有问题的状态信息。清屏,使用cls语句清屏。输出表头,使用title(" 考生答题 ")和endl语句输出表头,并使用scanf()函数读取用户输入的要抽取的题目数。使用for循环随机抽取题目,并使用getRandomProblem()函数从题库中随机抽取题目。输出题目和选项,并使用scanf()函数读取用户选择的答案,并使用convert()函数将其转换为大写字母。如果用户选择的答案与题目的正确答案相同,使用countRightProblems++语句将答对题目数加1。计算成绩,使用addNewStudent->grade = (double)(countRightProblems * 100) / countSelectedProblems语句计算学生得分,并使用addNewStudent->numQue = countSelectedProblems语句存储学生答题数目。清屏,使用cls语句清屏。输出表头,使用title(" 答题结果 ")和endl语句输出表头,并输出答对题目数和学生得分。使用saveStudents()函数保存学生数据,使用readStudents()函数重新读取学生数据。使用printf()函数输出提示信息,等待用户输入任意字符后返回操作界面。

四、课程设计结果

4.1主界面菜单

如图17所示,主菜单界面,共有保存退出、教师登录、学生登陆三个功能模块可供选择。

4.2教师登录界面

如图18所示,教师登录界面需要输入管理员账号和密码,默认账号为admin,默认密码为admin。

4.3教师管理界面

如图19所示,教师管理页面有四项选择,分别为退出、显示所有题目、管理题库和学生成绩查看。

4.4题库打印界面

如图20所示,教师可以打印题库中所有题目的信息。

4.5成绩打印界面

如图21所示,教师可以直接打印所有已答学生的信息。

4.6题库管理界面(增删改)

如图22、图23所示,教师管理功能涵盖更改题目、新增题目选项,其中更改题目先输入题目序号后,可进行删除和修改,修改可修改题目的标题、四个选项和答案。

4.7学生成绩信息存储到文件中

如图24所示,可以在同目录下的stu.txt文件中直接查看所有已答学生的数据。

4.8题目存储到文件中

如图25所示,可以在同目录下的test.txt文件中直接查看所有题目。

五、课程设计总结

C语言课程设计是一个很重要的环节,它可以帮助学生巩固所学的C语言知识,提高编程能力和实践能力。它通常要求学生独立完成一个小型的程序,要求程序具有一定的实用性和可行性,并且能够通过编译和测试。

选题是C语言课程设计的关键之一,学生可以根据自己的兴趣和爱好选择一个适合自己的主题。在选定了课程设计的主题之后,需要考虑程序的设计思路。可以先画出流程图和算法,然后再根据算法编写代码。在编写代码时,需要注意代码的规范性和可读性,要遵循一定的编程规范,例如命名规范、缩进规范等等。同时,还需要注意代码的注释,方便他人阅读和理解。在完成代码编写后,需要进行调试和测试,确保程序能够正常运行。在完成程序设计后,需要编写相关文档,包括程序说明、使用说明、设计思路、测试结果等等,以方便其他人理解和使用该程序。在其中,通过反复的debug,加深了对C语言链表的认识,更深入地认识到了链表的优越性。

在完成课程设计后,需要对整个过程进行总结和反思,分析自己在设计和编程过程中遇到的问题和解决方法,以及自己在编程能力和实践能力方面的提高程度。C语言课程设计可以帮助更好地理解和掌握C语言知识,提高编程能力和实践能力,是一个非常有意义的学习和实践过程。

附录:附源程序

//课题7、单项选择题标准化考试系统
//在磁盘上创建一个test.txt文件,使用记事本打开后先写入40道单选题和相应的选项和正确答案。再编写一个单项选择题标准化考试系统,该系统功能要求:
//A、用文件保存试题库。(每个试题包括题干、4个备选答案、标准答案)。
//B、试题录入、修改和删除。
//C、试题抽取:每次从试题库中可以随机抽出N道题(N由键盘输入)。
//D、考生答题:用户抽取N道试题进行答题,键盘输入每道题的答案。
//E、自动判卷:系统可根据用户答案与标准答案的对比实现判卷并给出成绩。
//系统操作过程中能够正确读取和更新试题文件,并进行正常的试题管理、抽取、答题和判卷操作,该过程在控制台中显示,人机交互方便。

#include 
#include 
#include 
#include 
#include 
#define cls system("cls")
#define endl puts("")
#define title(x) printf("============%s============\n", x)

typedef struct _Student { // 学生成绩链表存储
	char name[20], idx[20];
	int grade, numQue;
	_Student* next;
} Stu;

typedef struct _Problems { // 问题链表存储
	int idx, state;
	char q[50], op[4][25], ans;
	_Problems* next;
} Pro;

char convert(char c) { // 统一小写字母为大写字母
	if ('a' <= c && c <= 'z') c += 'A' - 'a';
	return c;
}

Pro* getRandomProblem(Pro* proHead, int countProblems) { // 抽取随机问题
	while (true) {
		srand(time(NULL));
		int selectedProblem = rand() % countProblems + 1;
		for (Pro* point = proHead->next; point != NULL; point = point->next) {
			if (point->idx == selectedProblem) {
				if (point->state) { // 判重
					break;
				} else {
					return point;
				}
			}
		}
	}
}

void clearState(Pro* proHead, int countProblems) { // 清空题目状态
	for (Pro* point = proHead->next; point != NULL; point = point->next) {
		point->state = 0;
	}
}

int readProblems(Pro* proHead) { // 读取题目
	FILE *fpReadProblems = fopen("test.txt", "r");

	char cnt[20];
	int countProblems = 0;
	fscanf(fpReadProblems, "%s", cnt);
	for (int i = 12; i < strlen(cnt); i ++) { // 将字符串转化为int型的题目数量
		char c = cnt[i];
		if ('0' <= c && c <= '9') {
			countProblems = countProblems * 10 + c - '0';
		} else {
			break;
		}
	}

	for (int i = 0; i < countProblems; i++) { // 读入题目
		Pro* readSavedProblems = (Pro*)malloc(sizeof(Pro));
		fscanf(fpReadProblems, "%s", readSavedProblems->q);
		for (int i = 0; i < 4; i ++) {
			fscanf(fpReadProblems, "%s", readSavedProblems->op[i]);
		}
		char ans[10];
		fscanf(fpReadProblems, "%s", ans);
		readSavedProblems->ans = ans[6]; // 读取答案
		readSavedProblems->idx = countProblems - i;

		readSavedProblems->next = proHead->next;
		proHead->next = readSavedProblems;
	}

	fclose(fpReadProblems);
	return countProblems;
}



int readStudents(Stu* stuHead) { // 读入学生成绩

	FILE *fpReadStudents = fopen("stu.txt", "r");
	char cnt[40];
	fscanf(fpReadStudents, "%s", cnt);
	int countStudents = 0;

	for (int i = 6; i < strlen(cnt); i ++) { // 将字符串转化为int型的已答学生数量
		char c = cnt[i];
		if ('0' <= c && c <= '9') {
			countStudents = countStudents * 10 + c - '0';
		} else {
			break;
		}
	}

	fscanf(fpReadStudents, "%*s%*s%*s%*s"); // 忽略表头
	for (int i = 0; i < countStudents; i ++) {
		Stu* readSavedStudents = (Stu*)malloc(sizeof(Stu));
		fscanf(fpReadStudents, "%s%s%d%d", readSavedStudents->name, readSavedStudents->idx, &readSavedStudents->grade, &readSavedStudents->numQue);
		readSavedStudents->next = stuHead->next;
		stuHead->next = readSavedStudents;
	}
	fclose(fpReadStudents);
	
	return countStudents;
}



void saveProblems(Pro* proHead, int countProblems) { // 保存题目
	FILE *fpWriteProblems = fopen("test.txt", "w");

	fprintf(fpWriteProblems, "当前有题目:%d个\n\n", countProblems);
	for (Pro* writeProblems = proHead->next; writeProblems != NULL; writeProblems = writeProblems->next) {
		fprintf(fpWriteProblems, "%s\n", writeProblems->q);
		for (int i = 0; i < 4; i ++) {
			fprintf(fpWriteProblems, "%s\n", writeProblems->op[i]);
		}
		fprintf(fpWriteProblems, "答案:%c\n\n", writeProblems->ans);
	}
	proHead->next = NULL;
	fclose(fpWriteProblems);

}



void saveStudents(Stu* stuHead, int countStudents) { // 保存学生成绩
	FILE *fpSaveStudents = fopen("stu.txt", "w");

	fprintf(fpSaveStudents, "总计有%d个学生已答题\n\n", countStudents);
	fprintf(fpSaveStudents, "姓名\t学号\t成绩\t题目\n");
	for (Stu* point = stuHead->next; point != NULL; point = point->next) {
		fprintf(fpSaveStudents, "%s\t%s\t%d\t%d\n", point->name, point->idx, point->grade, point->numQue);
	}
	stuHead->next = NULL;
	fclose(fpSaveStudents);

}



void outputProblems(Pro* proHead, int countProblems) { // 输出所有题目

	cls;
	title(" 题库题目 ");
	endl;
	for (Pro* outProblems = proHead->next; outProblems != NULL; outProblems = outProblems->next) {
		printf("%03d. %s\n", outProblems->idx, outProblems->q);
		for (int i = 0; i < 4; i ++) {
			printf("%s\n", outProblems->op[i]);
		}
		printf("答案:%c\n\n", outProblems->ans);
	}

	endl;
	printf("当前有题目:%d个\n\n", countProblems);
	endl;
	title(" 题库题目 ");
}



void outputStudents(Stu* stuHead, int countStudents) { // 输出所有学生

	cls;
	title(" 学生成绩表 ");
	endl;
	puts("姓名\t学号\t成绩\t题目");
	for (Stu* point = stuHead->next; point != NULL; point = point->next) {
		printf("%s\t%s\t%d\t%d\n", point->name, point->idx, point->grade, point->numQue);
	}
	endl;
	printf("已有%d人完成答题\n", countStudents);
	endl;
	title("===========");
}



void deleteProblem(Pro* proHead, int toBeDeleted) { // 删除指定题目
	Pro* last = proHead;
	for (Pro* point = proHead->next; point != NULL; last = point, point = point->next) {
		if (point->idx == toBeDeleted) {
			last->next = point->next;
			// free(point); // 回收内存
		}
	}
}



int editProblem(Pro* proHead, int countProblems, int problemToBeEdited) { // 编辑题目

	cls;
	title(" 变更题目 ");
	endl;
	puts("\t0 > 退出");
	puts("\t1 > 删除");
	puts("\t2 > 修改");
	endl;
	title("==========");

	int choose;
	scanf("%d", &choose);
	if (choose == 0) { // 选择更改题目与删除题目
		return countProblems;
	} else if (choose == 1) {
		countProblems--;
		deleteProblem(proHead, problemToBeEdited);
		return countProblems;
	}

	Pro* point;
	for (point = proHead->next; point != NULL; point = point->next) {
		if (point->idx == problemToBeEdited) {
			break;
		}
	}

	cls;
	title(" 变更题目 ");
	printf("题目为:%s\n你要修改成:(不变输入next)\n", point->q);
	char q2[50];
	scanf("%s", q2);
	if (strcmp(q2, "next") != 0) {
		strcpy(point->q, q2);
	}

	for (int i = 0; i < 4; i ++) {
		cls;
		title(" 变更题目 ");
		printf("%c 选项为:%s\n你要修改成:(不变输入next)\n", 'A' + i, point->op[i]);
		char op[25];
		scanf("%s", op);
		if (strcmp(op, "next") != 0) {
			sprintf(point->op[i], "%c.%s", 'A' + i, op);
		}
	}

	cls;
	printf("答案原为:%c\n你要修改成:(不变输入next)\n", point->ans);
	char anss[10];
	scanf("%s", anss);
	if (strcmp(anss, "next") != 0) {
		anss[0] = convert(anss[0]);
		point->ans = anss[0];
	}
	return countProblems;
}



int addProblem(Pro* proHead, int countProblems) { // 新增题目
	cls;
	title(" 新增题目 ");
	Pro* addNewProblems = (Pro*)malloc(sizeof(Pro));
	printf("题  目:");
	scanf("%s", addNewProblems->q);
	for (int i = 0; i < 4; i ++) {
		printf("%c 选项:", 'A' + i);
		scanf("%s", addNewProblems->op[i]);
		char index[30] = "";
		sprintf(index, "%c.%s", 'A' + i, addNewProblems->op[i]);
		strcpy(addNewProblems->op[i], index);
	}
	char ans;
	printf("答  案:");
	getchar();
	scanf("%c", &ans);
	ans = convert(ans);
	addNewProblems->ans = ans;
	addNewProblems->next = proHead->next;
	proHead->next = addNewProblems;
	countProblems ++;
	return countProblems;
}



int manageProblems(Pro* proHead, int countProblems) { // 管理题库

	cls;
	title(" 管理题目 ");
	endl;
	puts("\t0 > 退出");
	puts("\t1 > 更改题目");
	puts("\t2 > 新增题目");
	endl;
	title("==========");

	int chooseManageProblems;
	scanf("%d", &chooseManageProblems);
	switch (chooseManageProblems) {
		case 0:
			return countProblems;
		case 1:
			outputProblems(proHead, countProblems);
			printf("请选择要更改的题目编号:");
			int problemToBeEdited;
			scanf("%d", &problemToBeEdited);
			countProblems = editProblem(proHead, countProblems, problemToBeEdited);
			break;
		case 2:
			countProblems = addProblem(proHead, countProblems);
			break;
	}
	
	return countProblems;
}



int startTeacher(Stu* stuHead, Pro* proHead, int countStudents, int countProblems) { // 老师管理界面

	while (true) {
		cls;
		title(" 教师登录 ");
		endl;
		char account[20], password[20];
		const char acc[20] = "admin", pswd[20] = "admin";
		printf("\t账号:");
		scanf("%s", account);
		printf("\t密码:");
		scanf("%s", password);
		if (strcmp(account, acc) == 0 && strcmp(password, pswd) == 0) {
			break;
		}
		cls;
		title(" 教师登录 ");
		printf("\n\t账号或密码错误\n\n");
		title("==========");
		Sleep(1500);
	}

	while (true) {
		cls;
		title(" 教师管理 ");
		endl;
		puts("\t0 > 退出");
		puts("\t1 > 显示所有题目");
		puts("\t2 > 管理题库");
		puts("\t3 > 学生成绩查看");
		endl;
		title("==========");

		int choooseTeacherManage;
		scanf("%d", &choooseTeacherManage);
		switch (choooseTeacherManage) {
			case 0:
				return countProblems;
			case 1:
				outputProblems(proHead, countProblems);
				printf("输入任意字符退出:");
				char c2quit[20];
				scanf("%s", c2quit);
				break;
			case 2:
				countProblems = manageProblems(proHead, countProblems);
				break;
			case 3:
				outputStudents(stuHead, countStudents);
				printf("输入任意字符退出:");
				char c3quit[20];
				scanf("%s", c3quit);
				break;
		}
		saveProblems(proHead, countProblems);
		countProblems = readProblems(proHead);
	}


}



void startStudent(Stu* stuHead, Pro* proHead, int countStudents, int countProblems) { // 学生答题界面

	Stu* addNewStudent = (Stu*)malloc(sizeof(Stu));
	
	cls;
	title(" 考生登录 ");
	endl;
	printf("\t姓名:");
	scanf("%s", addNewStudent->name);
	printf("\t学号:");
	scanf("%s", addNewStudent->idx);

	clearState(proHead, countProblems);

	cls;
	title(" 考生答题 ");
	endl;
	printf("\t要抽取题目数:");
	int countSelectedProblems, countRightProblems = 0;
	scanf("%d", &countSelectedProblems);

	for (int i = 0; i < countSelectedProblems; i ++) {
		cls;
		title(" 考生答题 ");
		endl;

		Pro* selectedProblem = getRandomProblem(proHead, countProblems);

		printf("第%d题:%s\n", i + 1, selectedProblem->q);
		for (int i = 0; i < 4; i ++) {
			printf("%s\n", selectedProblem->op[i]);
		}

		printf("\n选择:");

		getchar();
		char choose;
		scanf("%c", &choose);
		choose = convert(choose);
		if (choose == selectedProblem->ans) {
			countRightProblems ++;
		}
	}
	addNewStudent->grade = (double)(countRightProblems * 100) / countSelectedProblems;
	addNewStudent->numQue = countSelectedProblems;
	cls;
	title(" 答题结果 ");
	endl;
	printf("\t答对:%d/%d\n", countRightProblems, countSelectedProblems);
	printf("\t得分:%d分\n", addNewStudent->grade);
	puts("\t更新数据中,请稍后");
	endl;
	title("==========");

	// 更新数据
	addNewStudent->next = stuHead->next;
	stuHead->next = addNewStudent;
	countStudents ++;
	saveStudents(stuHead, countStudents);
	countStudents = readStudents(stuHead);
	
	printf("输入任意字符退出:");
	char c4quit[20];
	scanf("%s", c4quit);
}



int main() {

	while (true) { // 保持不退出

		cls;
		title(" 单项选择题标准化考试系统 ");
		endl;
		puts("\t\t0 > 保存退出");
		puts("\t\t1 > 教师登录");
		puts("\t\t2 > 学生登录");
		endl;
		title("==========================");

		// 初始化学生和问题链表
		Stu *stuHead = (Stu *)malloc(sizeof(Stu));
		Pro *proHead = (Pro *)malloc(sizeof(Pro));
		stuHead->next = NULL;
		proHead->next = NULL;

		int countProblems = readProblems(proHead);
		int countStudents = readStudents(stuHead);
		int chooseTorS;

		scanf("%d", &chooseTorS);
		switch (chooseTorS) {
			case 0:
				cls;
				title(" 单项选择题标准化考试系统 ");
				endl;
				puts("\t\t      已退出");
				endl;
				title("==========================");
				return 0;
			case 1:
				countProblems = startTeacher(stuHead, proHead, countStudents, countProblems);
				break;
			case 2:
				startStudent(stuHead, proHead, countStudents, countProblems);
				break;
		}
		saveProblems(proHead, countProblems);

	}

	return 0;
}

你可能感兴趣的:(c语言,课程设计)