导入时图片加载异常,故本文图例和图片均已隐去,只保留了功能结构图。
程序设计环境:Visual Studio Code, Sublime 4。
一、课程设计任务
1.1课题背景
本项目旨在开发一个单项选择题标准化考试系统,能够方便地管理试题库,抽取试题,进行答题和自动判卷。该系统的实现需要借助计算机编程技术和文件操作技术,以实现试题库的管理和试题的抽取、答题和判卷等功能。考生可以通过该系统进行模拟考试,系统能够根据考生的答案和标准答案的对比,自动判卷并给出成绩,为考生提供便利和准确的考试评估。
该系统的开发旨在提高考试管理的效率和准确性,同时为考生提供更加便捷和准确的考试体验。同时,该系统也可以应用于各种类型的考试,如教育机构的入学考试、职业资格考试等,具有广泛的应用前景和市场价值。
本系统完全独立开发,力求使系统功能简洁明了,且功能齐全且易于操作。
1.2目的背景和意义
开发这个项目的主要目的是提供一个方便、高效、可靠的单项选择题标准化考试系统,以解决传统考试方式中存在的一些问题,如试题管理不便、考试效率低下、判卷难度大等。
提高考试管理的效率和准确性。传统的考试方式中,试题的管理、抽取、答题和判卷等环节都需要人工操作,存在较大的误差和不确定性。而该系统能够实现试题库的自动管理和试题的自动抽取、判卷等功能,大大提高了考试管理的效率和准确性。
提高考试的公平性和准确性。通过该系统进行考试,每个考生所抽取的试题都是随机的,试题的难度和内容分布也是随机的,因此可以有效避免试题的不公平性和偏差性。同时,系统能够自动判卷,减少了人工判卷所产生的误差和主观性,提高了考试的准确性和公正性。
提高考生的答题效率和体验。传统考试方式中,考生需要在试卷上标出答案,容易出现漏填、误填等情况,同时也需要手动计算分数和核对答案,效率低下。而该系统能够自动记录考生答案并自动判卷,大大提高了答题效率和准确性,同时也提供了更加便捷和舒适的考试体验。
具有广泛的应用前景和市场价值。考试是社会各行业和领域中必不可少的一环,如教育、招聘、职业资格认证等。该系统具有广泛的应用前景和市场价值,可以为各种类型的考试提供方便、快捷、准确的考试管理和评估服务,为社会提供更加高效、公正、科学的考试方式,对于提高社会整体素质和人才培养也具有积极的推动作用。
1.3项目开发的目标
集成题库管理系统和单项选择题答题,采用计算机对学生答题进行管理,进一步提高办学效益和现代化水平。帮助广大教师提高工作效率,实现学生答题工作流程的系统化、规范化和自动化。
二、总体设计方案
2.1单项选择题标准化考试系统的功能
如图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;
}