C语言项目实战:学生成绩管理系统

前言

在我们学习了C语言之后,总会想着自己来手搓一个一个小项目来试验一下自己的能力,要努力但是不要着急,凡事都要有个过程。但往往一个项目的实现是从零开始的,从零开始打造一个属于你自己的代码王国。

一个学生成绩管理系统说难也不难,我的代码也不是很多,也就写了900多行,功能也不算很多,因为这个项目是主要是用来练习链表的,所以我里面的大部分东西都是用的链表来实现,用链表的话你就可以一直套娃,就拓展性比较强,在你某天突发奇想想要加什么东西上去的时候,就可以在链表的尾部加个东西上去。

废话不多说,下面进入代码的介绍,首先我进行我代码的一个整体的讲解。我的代码在运行之后,首先会让你选择是否进入这个系统,如果选择了否,就会直接退出整个程序,选择了是的话,就会进入密码输入环节,密码输入的时候,你是看不到你输入的密码的喔(保密保密ヽ(*´з`*)ノ),你只能看到“*”,如果你密码输入错误,会提示你密码输入错误,请重新输入,你也可以输入“exit”,直接退出程序。

当密码输入正确后,就会进入一个菜单了,这个菜单里面你能选择你下一步的操作,我的这个菜单是运用了一个switch来进行选择(完全程序不考虑会不会很卡(/ω\)),进行switch的进行菜单选择的原因是我觉得switch的拓展性比较强,而且在后面实现我的一个功能也比较方便٩( 'ω' )و ,在进行switch选择的时候,你们可以自己后续拓展更多的功能,或者你觉得我的这些功能不好,你也可以自己去改这些功能,在这个菜单里面直接跳转定义到对应的函数就行了,别说有多方便了| ू•ૅω•́)ᵎᵎᵎ。在菜单的选择之中,你也可以输入“exit”来退出这个系统(其实也可以直接点❌退出程序,这个只是为了更好的贴合一个完整的成绩管理系统,上述我说要实现的功能就是这个),具体的菜单的功能,在下面我再详细来讲,为了照顾一下大众,也为了更好的讲述如何更好的构思出项目,那我就从零开始慢慢的讲述了。

首先是头文件的引入

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>

然后是学生的信息的结构体,使用结构体方便我们进行学生信息的输入和查找等

struct Student {
	//学生的学号
	int studentID;
	char studentName[50];
	float mathGrade;	//数学
	float englishGrade;	//英语
	float dataStructureGrade;	//数据结构
	float computerOrganizationGrade;	//计算机组成原理
	float probabilityGrade;		//概率论
	float physicsGrade;			//物理
	float total; 		// 添加总成绩字段
	struct Student *next;
};

随后一些都是底层的一些基础操作

比如链表的插入和删除等,在这些的基础上,我们才能进行更高级的操作,所以底层代码一定要先写出来,代码我就不一一解释了,大多数代码我都有注释,我只讲大致的思路。

//链表判空操作
int isListEmpty(struct Student *head) {
	return (head == NULL);
}

// 创建新学生节点
struct Student *createStudent(int id, const char *name, float math, float english, float ds, float co,

                              float probability, float physics) {

	//malloc,给新的学生分配新的节点开辟一个
	//sizeof,计算一个结点所占空间的大小
	struct Student *newStudent = (struct Student *)malloc(sizeof(struct Student));


	//由于系统资源有限,分配可能会失败。为了确保程序能够处理这种情况
	//需要对内存的分配进行一次检查
	if (newStudent == NULL) {
		printf("内存分配失败。\n");
		exit(1);
	}

	//新学生的学号
	newStudent->studentID = id;

	//新学生的姓名
	snprintf(newStudent->studentName, sizeof(newStudent->studentName), "%s", name);

	//高数的成绩
	newStudent->mathGrade = math;

	//英语的成绩
	newStudent->englishGrade = english;

	//数据结构的成绩
	newStudent->dataStructureGrade = ds;

	//计算机组成原理的成绩
	newStudent->computerOrganizationGrade = co;

	//概率论的成绩
	newStudent->probabilityGrade = probability;

	//大物的成绩
	newStudent->physicsGrade = physics;

	//将下一个节点的指针设置为空指针,因为目前没有下一个节点
	newStudent->next = NULL;

	//返回指向新学生节点的指针
	return newStudent;
}

// 插入学生节点到链表末尾
void insertStudent(struct Student **head, struct Student *newStudent) {

	if (*head == NULL) {
		*head = newStudent;

	} else {
		struct Student *temp = *head;
		while (temp->next != NULL) {
			temp = temp->next;
		}
		//将新学生结点插入
		temp->next = newStudent;
	}
}

// 根据学号删除学生节点
void deleteStudent(struct Student **head, int id) {
	struct Student *current = *head, *prev = NULL;

	//遍历成功,删除结点
	if (current != NULL && current->studentID == id) {
		*head = current->next;
		free(current);
		return;
	}

	//学号匹配,一直遍历
	while (current != NULL && current->studentID != id) {
		prev = current;
		current = current->next;
	}

	if (current == NULL) {
		printf("未找到学号为 %d 的学生。\n", id);
		return;
	}

	//如果找到匹配的,删除当前结点
	prev->next = current->next;
	free(current);
}

随后就是最重要的功能函数

最重要的一部分就是我的功能函数,在功能函数里面,是通过一个swtich函数来选择各部分的功能,接下来我来一一列出各部分功能的实现。

void Function(void) {
	char target[10];
	int flag1, flag2;
	struct Student *newStudent;
	int flag3, flag4, flag5, flag6, flag7;
	int subjectIndex;


	printf("\n请选择下一步操作(输入 exit 退出程序):\n");
	printf("1. 查询学生信息           2. 插入学生信息\n");
	printf("3. 删除信息               4. 输出所有学生成绩到文件中\n");
	printf("5. 打印全部学生的信息     6. 查询学生平均成绩\n");
	printf("7. 从外部文件读取学生信息 8. 输出总成绩排名\n");
	printf("9. 输出排名奖状到文件中\n");
	scanf("%s", target);

	if (isExitCommand(target)) {
		printf("退出程序。\n");
		exit(1);
	}
	clearScreen();

	switch (target[0]) {
		case '1':
			printf("现在进行查询:\n");
			printf("请输入要查询的学号:");
			scanf("%d", &flag1);
			if (printStudent(head, flag1)) {
				printf("请问是否需要将学生的成绩输出到文件中\n");
				printf("1.是         2.否\n");
				scanf("%d", &flag4);

				if (flag4 == 1) {
					printStudentToFile(head, flag1, "student_grades.txt");
					printf("输出完成\n");
				}
			}
			if (isListEmpty(head)) {
				printf("目前系统内并无学生信息");
			}
			waitForEnter();
			break;
		case '2':
			printf("现在进行学生信息的输入\n");
			newStudent = inputStudent();
			head = addToLinkedList(head, newStudent);
			printf("输入完成");
			waitForEnter();
			break;
		case '3':
			printf("现在进行学生信息的删除\n");
			printf("请问是否需要打印全部学生的信息:\n");
			printf("1.是      2.否\n");
			scanf("%d", &flag3);

			if (flag3 == 1) {
				printf("\n所有学生信息:\n");
				printStudents(head);
			}

			printf("请输入要删除学生的学号:");
			scanf("%d", &flag2);
			deleteStudent(&head, flag2);
			waitForEnter();
			break;
		case '4':
			printStudentsToFile(head, "Students_Graph.txt");
			printf("输出完成\n");
			waitForEnter();
			break;
		case '5':
			printStudents(head);
			waitForEnter();
			break;
		case '6':
			printf("是否输出全部学生的总平均成绩:\n");
			printf("1.是         2.否\n");
			scanf("%d", &flag5);
			if (flag5 == 1) {
				printStudentsTotalAverage(head);
				waitForEnter();
				break;
			} else {
				printf("请输入要输出学生的学号:");
				scanf("%d", &flag6);
				printStudentWithAverage(head, flag6);
				waitForEnter();
				break;
			}
		case '7':
			loadStudentsFromFile("student_info.txt", &head);
			printf("读取完成\n");
			waitForEnter();
			break;
		case '8':
			printf("按成绩从大到小的排名为:\n");
			rankStudentsByTotal(head);
			printf("是否需要输出单个科目的排名\n");
			printf("1.是        2.否\n");
			scanf("%d", &flag7);
			if (flag7 == 1) {
				printf("请选择科目(1.数学 2.英语 3.数据结构 4.计算机组成原理 5.概率论 6.物理): ");
				scanf("%d", &subjectIndex);

				if (subjectIndex < 1 || subjectIndex > 6) {
					printf("无效的科目选择。\n");
					waitForEnter();
					break;

				} else {
					rankStudentsBySubject(head, subjectIndex - 1);
					waitForEnter();
					break;
				}
			} else {
				waitForEnter();
				break;
			}
		case '9':
			generateCertificates(head);
			printf("输出完成\n");
			waitForEnter();
			break;
		default :
			printf("输入有误,请重新输入");
			waitForEnter();
			break;
	}
	clearScreen();
}

首先是第一部分查询学生信息的实现:

第一部分是我们的查询功能,在我们这个系统中,当我们选择了我们需要的功能之后,会进行一次清屏操作,就是只会显示当前你选择的功能的内容,使界面显得比较简洁,没有那么凌乱。我们输入我们要查询的学号,然后通过printStudent();对链表进行遍历,将对应学号的学生的信息打印出来,如果在链表内无该学生的学号,则会打印出未找到该名学生。打印出来之后,我又设置了是否需要将该名学生的信息输出到文件中,通过printStudentToFile();将学生的信息输出到student_grades.txt文本文档中,输入完成再按一次回车,则会返回到我们功能函数界面,再次选择你所需要的功能,在这个函数里面使用到的一些函数后续会继续介绍。

打印对应学生学号的信息

// 打印指定学生信息
int printStudent(struct Student *head, int targetID) {
	struct Student *temp = head;
	float total = 0.0;
	while (temp != NULL) {

		//学号匹配

		if (temp->studentID == targetID) {
			// 计算总成绩
			total = calculateTotalGrade(temp);

			// 打印学生信息
			printf("学号:%d, 姓名:%s, 数学:%.2f, 英语:%.2f, 数据结构:%.2f, 计算机组成原理:%.2f, 概率论:%.2f, 物理:%.2f, 总成绩:%.2f\n",
			       temp->studentID, temp->studentName, temp->mathGrade, temp->englishGrade, temp->dataStructureGrade,
			       temp->computerOrganizationGrade, temp->probabilityGrade, temp->physicsGrade, total);
			return 1;  // 找到学号匹配的学生后直接返回,不需要继续遍历
		}
		//如果当前学号不匹配,移动到下一个结点
		temp = temp->next;
	}
	printf("未找到学号为%d的学生。\n", targetID);
	return 0;
}

输出学生信息到文本文档中

fopen();函数的参数,第一个是文件的名字,如果文件不存在,则会自动创建一个文件,第二个参数是打开的文件的名字,“w”是写模式,“r”是只读模式

//输出对应学号的学生到文本文件中
void printStudentToFile(struct Student *head, int studentID, const char *filename) {
	//打开为写模式
	FILE *file = fopen(filename, "w");
	if (file == NULL) {
		printf("无法打开文件 %s\n", filename);
		exit(1);
	}

	struct Student *temp = head;
	while (temp != NULL) {

		if (temp->studentID == studentID) {
			//写入文件
			fprintf(file, "学号\t姓名\t数学\t英语\t数据结构\t计算机组成原理\t概率论\t物理\n");
			fprintf(file, "%d\t%s\t%.2f\t%.2f\t%.2f\t %.2f\t        %.2f  \t%.2f\n",
			        temp->studentID, temp->studentName, temp->mathGrade, temp->englishGrade,
			        temp->dataStructureGrade, temp->computerOrganizationGrade, temp->probabilityGrade,
			        temp->physicsGrade);
			fclose(file);	//关闭文件
			return;
		}
		temp = temp->next;
	}

	// 学号不存在
	printf("找不到学号为 %d 的学生。\n", studentID);
	fclose(file);
}

等待回车

等待回车的函数在接下来的每个功能分函数都会用到,我这里只列出一个

// 等待回车的函数
void waitForEnter() {
	printf("按回车键以继续.......\n");
	while (getchar() != '\n');  // 消耗输入缓冲区中的回车符
	getchar();  // 等待用户按回车
}

第二部分输入学生信息

输入学生的信息通过inputStudent();来实现,你只要输入对应学生的信息,输入完成之后就会返回一个学生节点指针,我们首先要先创建一个新的学生指针newStudent,当输入完成之后,我写了一个检查学生学号是否重复的函数,检查学生是否已经插入了,如果重复之后,系统会提示是否打印所有学生信息,以检查学生是否已经插入,检查没问题之后就开始进行新的学生节点的插入了,使用addToLinkedList();头插法来插入新节点。

输入学生信息

// 输入学生信息并返回学生节点指针
struct Student *inputStudent() {
	struct Student *newStudent = createStudent(0, "", 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);

	//
	int flag;
	do {
		int studentID;
		printf("请输入学生学号:");
		scanf("%d", &studentID);

		// 检查学号是否重复
		if (isStudentIDDuplicate(head, studentID)) {
			printf("学号重复,检查是否已插入。\n");
			printf("请选择下一步:\n");
			printf("1.继续插入  2.打印所有学生的信息\n");

			scanf("%d", &flag);

			if (flag == 1) {
				continue; // 如果学号重复,则重新输入

			} else if (flag == 2) {
				// 打印所有学生信息
				printf("\n所有学生信息:\n");
				printStudents(head);
				printf("\n");
				continue;
			}
		}

		newStudent = createStudent(studentID, "", 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
		break; // 学号不重复,退出循环
	} while (1);

	printf("请输入学生姓名:");
	scanf("%s", newStudent->studentName);

	printf("请输入数学成绩:");
	scanf("%f", &(newStudent->mathGrade));

	printf("请输入英语成绩:");
	scanf("%f", &(newStudent->englishGrade));

	printf("请输入数据结构与算法成绩:");
	scanf("%f", &(newStudent->dataStructureGrade));

	printf("请输入计算机组成原理成绩:");
	scanf("%f", &(newStudent->computerOrganizationGrade));

	printf("请输入概率论与数理统计成绩:");
	scanf("%f", &(newStudent->probabilityGrade));

	printf("请输入大学物理成绩:");
	scanf("%f", &(newStudent->physicsGrade));

	newStudent->next = NULL;

	return newStudent;
}

头插法插入新学生节点

// 将学生加入链表
struct Student *addToLinkedList(struct Student *head, struct Student *newStudent) {
	if (newStudent != NULL) {
		newStudent->next = head;
		head = newStudent;
	}
	return head;
}

检查学号是否重复

struct Student *head = NULL;

// 检查学号是否重复
int isStudentIDDuplicate(struct Student *head, int studentID) {
	struct Student *temp = head;
	while (temp != NULL) {
		if (temp->studentID == studentID) {
			return 1; // 学号重复
		}
		temp = temp->next;
	}
	return 0; // 学号不重复
}

第三部分学生信息的删除

学生信息的删除对于链表来说比较简单,因为链表的内存是随机分配的,并不是连续的,所以他删除的时候可以直接删除,不需要像数组一样,还要将后面的内容一个一个移动到前面去,大大增加了工作量。进行学生信息的删除时,该系统首先会询问你是否要打印全部学生的信息,如果选择了是,就会通过printStudents(head);按顺序一个一个的将学生的信息打印出来。输入要删除学生的学号,随后会遍历链表内的所有学生的学号信息,然后找到对应学生的信息进行删除,该函数我们在一开始的底层函数就已经写了,是deleteStudent();所以我们可以直接使用,这里就不列出来了,读者可自己往前翻看。请注意,删除了学生节点之后,记得要将我们已经删除的学生节点进行内存释放,因为链表的内存是动态分配的,你如果不释放内存的话,可能会造成溢出。

打印所有学生信息

// 打印所有学生信息
void printStudents(struct Student *head) {
	struct Student *temp = head;
	float total = 0.0;
	while (temp != NULL) {
		// 计算总成绩
		total = calculateTotalGrade(temp);

		// 打印学生信息
		printf("学号:%d, 姓名:%s, 数学:%.2f, 英语:%.2f, 数据结构:%.2f, 计算机组成原理:%.2f, 概率论:%.2f, 物理:%.2f, 总成绩:%.2f\n",
		       temp->studentID, temp->studentName, temp->mathGrade, temp->englishGrade, temp->dataStructureGrade,
		       temp->computerOrganizationGrade, temp->probabilityGrade, temp->physicsGrade, total);
		temp = temp->next;
	}
}

第四部分输出所有学生信息到文本文档

输出所有学生信息到文本文档的操作和前面的一个函数很相似,就是输出对应学生学号的信息到文本文档的函数,这个输出所有学生信息的函数还更简洁了一点,因为这个函数他不需要一个一个遍历学生的学号。

输出所有学生信息

// 打印所有学生信息到文件
void printStudentsToFile(struct Student *head, const char *filename) {
	FILE *file = fopen(filename, "w");
	if (file == NULL) {
		printf("无法打开文件 %s\n", filename);
		exit(1);
	}

	fprintf(file, "学号\t姓名\t数学\t英语\t数据结构\t计算机组成原理\t概率论\t物理\n");
	struct Student *temp = head;
	while (temp != NULL) {
		fprintf(file, "%d\t%s\t%.2f\t%.2f\t%.2f\t %.2f\t        %.2f  \t%.2f\n",
		        temp->studentID, temp->studentName, temp->mathGrade, temp->englishGrade,
		        temp->dataStructureGrade, temp->computerOrganizationGrade, temp->probabilityGrade,
		        temp->physicsGrade);
		temp = temp->next;
	}

	fclose(file);
}

第五部分打印所有学生信息

作为一个学生成绩管理系统,那肯定要有一个直观的能看到所有学生信息的一个操作的,所以我第五部分是给了一个打印所有学生信息的功能的。这部分的代码也是比较简洁的,比较需要的操作也不多,这个函数也在前面的删除学生信息的功能里面用过了,所以这里也算啰嗦了。

打印所有学生信息

// 打印所有学生信息
void printStudents(struct Student *head) {
	struct Student *temp = head;
	float total = 0.0;
	while (temp != NULL) {
		// 计算总成绩
		total = calculateTotalGrade(temp);

		// 打印学生信息
		printf("学号:%d, 姓名:%s, 数学:%.2f, 英语:%.2f, 数据结构:%.2f, 计算机组成原理:%.2f, 概率论:%.2f, 物理:%.2f, 总成绩:%.2f\n",
		       temp->studentID, temp->studentName, temp->mathGrade, temp->englishGrade, temp->dataStructureGrade,
		       temp->computerOrganizationGrade, temp->probabilityGrade, temp->physicsGrade, total);
		temp = temp->next;
	}
}

第六部分查询学生的平均成绩

这部分功能函数有两种选择,一种是直接输出所有学生对应的平均成绩,然后直接输入全部学生的总平均分。另外一种是输入对应学生的学号,然后查询该名学生的平均成绩。输出所有学生的平均分和输出单个学生的平均分的函数都是差不多的,都是先计算各科成绩加起来的总分,然后在算平均分。

计算单个学生总成绩

// 计算学生总成绩
float calculateTotalGrade(struct Student *student) {
	return student->mathGrade +
	       student->englishGrade +
	       student->dataStructureGrade +
	       student->computerOrganizationGrade +
	       student->probabilityGrade +
	       student->physicsGrade;
}

输出所有学生的平均分

// 打印所有学生信息及总成绩平均分
void printStudentsTotalAverage(struct Student *head) {
	struct Student *temp = head;
	float totalSum = 0.0;
	int studentCount = 0;

	while (temp != NULL) {
		// 计算总成绩
		float total = calculateTotalGrade(temp);
		float average = total / 6;
		totalSum += total;

		// 打印学生信息和总成绩
		printf("学号:%d, 姓名:%s, 数学:%.2f, 英语:%.2f, 数据结构:%.2f, 计算机组成原理:%.2f, 概率论:%.2f, 物理:%.2f, 总成绩:%.2f,平均分:%.2f\n",
		       temp->studentID, temp->studentName, temp->mathGrade, temp->englishGrade, temp->dataStructureGrade,
		       temp->computerOrganizationGrade, temp->probabilityGrade, temp->physicsGrade, total, average);

		temp = temp->next;
		studentCount++;
	}

	if (studentCount > 0) {
		float average = totalSum / studentCount;
		printf("\n所有学生总成绩平均分:%.2f\n", average);
	} else {
		printf("\n暂无学生信息。\n");
	}
}

输出对应学生的平均分

// 打印指定学生信息及其平均分
void printStudentWithAverage(struct Student *head, int targetID) {
	struct Student *temp = head;
	float total = 0.0;
	int subjectCount = 0;

	while (temp != NULL) {
		if (temp->studentID == targetID) {
			// 计算总成绩
			total = calculateTotalGrade(temp);

			// 打印学生信息
			printf("学号:%d, 姓名:%s, 数学:%.2f, 英语:%.2f, 数据结构:%.2f, 计算机组成原理:%.2f, 概率论:%.2f, 物理:%.2f, 总成绩:%.2f\n",
			       temp->studentID, temp->studentName, temp->mathGrade, temp->englishGrade, temp->dataStructureGrade,
			       temp->computerOrganizationGrade, temp->probabilityGrade, temp->physicsGrade, total);
			subjectCount++;
			break;  // 找到学生后直接退出循环
		}
		temp = temp->next;
	}

	if (subjectCount > 0) {
		float average = total / 6.0;  // 每个学生有六门课程
		printf("该学生总成绩平均分:%.2f\n", average);
	} else {
		printf("未找到学号为 %d 的学生。\n", targetID);
	}
}

第七部分从外部文件读取学生信息

在学生成绩管理系统中,在外部文件中导入学生信息的操作是很常用的,在这个系统中,我们首先要创建一个名叫“student_info.txt”的文本文档,然后将对应学生的信息一一输入到这个文本文档中,系统就能将学生的信息读取,然后创建一个新的学生节点进行插入。这个功能函数可以一下子导入多名学生的信息,只要你注意每个学生信息的间距就行,你输入一个信息之后,你要用空格隔开,不然可能会出现乱码的现象。在这个导入的功能函数当中,我测试的时候发现了一个问题,就是你只能输入英文的名字,如果你输入中文的名字,当你导入到系统之后,你的学生的名字就是一堆乱码,后来我想了想解决的方法,只要你把文字的格式改一下就好了,改成UTF-8或者其他的格式,使用这个函数就可以实现了setlocale();但是我试过几种格式都不能解决这个问题,如果读者感兴趣可以自行去尝试弄一下这个问题。在从外部输入学生的信息之后,我还使用了检查学生学号是否重复的函数,如果学号重复了,系统就会打印出学生重复的学生的学号。

从外部文件读取学生信息

// 从外部文本文档读取学生信息并录入
void loadStudentsFromFile(const char *filename, struct Student **head) {
	setlocale(LC_ALL, "chs");
	FILE *file = fopen(filename, "r");
	if (file == NULL) {
		printf("无法打开文件 %s\n", filename);
		exit(1);
	}

	int studentID;
	char studentName[50];
	float mathGrade, englishGrade, dsGrade, coGrade, probabilityGrade, physicsGrade;

	while (fscanf(file, "%d %49s %f %f %f %f %f %f",
	              &studentID, studentName, &mathGrade, &englishGrade, &dsGrade, &coGrade, &probabilityGrade, &physicsGrade) == 8) {
		struct Student *newStudent = (struct Student *)malloc(sizeof(struct Student));
		if (newStudent == NULL) {
			printf("内存分配失败。\n");
			exit(1);
		}

		newStudent->studentID = studentID;
		snprintf(newStudent->studentName, sizeof(newStudent->studentName), "%s", studentName);
		newStudent->mathGrade = mathGrade;
		newStudent->englishGrade = englishGrade;
		newStudent->dataStructureGrade = dsGrade;
		newStudent->computerOrganizationGrade = coGrade;
		newStudent->probabilityGrade = probabilityGrade;
		newStudent->physicsGrade = physicsGrade;
		newStudent->next = NULL;

//		 检查学号是否重复
		if (isStudentIDDuplicate(*head, studentID)) {
			printf("\n");
			printf("警告:学号为 %d 的学生信息重复!\n", studentID);
			printf("重复的学生信息:\n");
			printStudent(newStudent, studentID);
			printf("\n");
		}

		// 将新学生添加到链表
		if (*head == NULL) {
			*head = newStudent;
		} else {
			struct Student *temp = *head;
			while (temp->next != NULL) {
				temp = temp->next;
			}
			temp->next = newStudent;
		}



	}

	fclose(file);
}

第八部分输出总成绩排名

在写这个总成绩排名时,我使用的是最淳朴原始的方式来写,就是创建一个数组,然后将各个学生的信息放入数组,随后用冒泡排序来进行总成绩的排名,冒泡排序算是最原始的一种排序方式了,这个算法的缺点有很多,特别是当系统中学生的总数很多的时候,这时候你的冒泡排序就显得十分吃力了,读者可自己将这个算法改进,比如你可以插入的时候直接使用二叉排序树来插入,这样你输入的时候就可以直接一个前序遍历来实现排名了,但是二叉排序的缺点也很明显,就是你插入的时候会非常麻烦,所以当时我在考量的时候,也只选了这个冒泡排序来实现,不过如果读者有能力的话,可以直接使用堆排序来进行学生总成绩的排序,这部分就等读者来自行改进了,毕竟比冒泡排序高级的算法多得去了。输出总成绩排名之后,也可以选择一个单科成绩的排名,选择对应的科目,就可以将对应科目的成绩排名输出。

以总分从大到小输出学生排名

// 以总分从大到小的顺序对学生进行排名
void rankStudentsByTotal(struct Student *head) {
	// 计算学生总分
	struct Student *temp = head;
	while (temp != NULL) {
		// 将各科成绩相加赋值给 total 字段
		temp->total = temp->mathGrade + temp->englishGrade +
		              temp->dataStructureGrade + temp->computerOrganizationGrade +
		              temp->probabilityGrade + temp->physicsGrade;
		temp = temp->next;
	}

	// 将学生信息放入数组
	int numStudents = 0;
	temp = head;
	while (temp != NULL) {
		numStudents++;
		temp = temp->next;
	}

	struct Student **studentsArray = (struct Student **)malloc(numStudents * sizeof(struct Student *));
	if (studentsArray == NULL) {
		printf("内存分配失败。\n");
		exit(1);
	}

	int i = 0;
	temp = head;
	while (temp != NULL) {
		studentsArray[i] = temp;
		i++;
		temp = temp->next;
	}

	// 冒泡排序,按总分从大到小排列
	for (int j = 0; j < numStudents - 1; j++) {
		for (int k = 0; k < numStudents - j - 1; k++) {
			if (studentsArray[k]->total < studentsArray[k + 1]->total) {
				// 交换两个学生的位置
				struct Student *tempStudent = studentsArray[k];
				studentsArray[k] = studentsArray[k + 1];
				studentsArray[k + 1] = tempStudent;
			}
		}
	}

	// 输出排名信息
	printf("排名\t学号\t姓名\t数学\t英语\t数据结构\t计算机组成原理\t概率论\t物理\t总分\n");
	for (int j = 0; j < numStudents; j++) {
		printf("%d\t%d\t%s\t%.2f\t%.2f\t%.2f\t\t%.2f\t\t%.2f\t%.2f\t%.2f\n", j + 1, studentsArray[j]->studentID,
		       studentsArray[j]->studentName, studentsArray[j]->mathGrade, studentsArray[j]->englishGrade,
		       studentsArray[j]->dataStructureGrade, studentsArray[j]->computerOrganizationGrade,
		       studentsArray[j]->probabilityGrade, studentsArray[j]->physicsGrade, studentsArray[j]->total);
	}
	// 释放内存
	free(studentsArray);
}

单科科目排名

// 按照特定科目排名学生
void rankStudentsBySubject(struct Student *head, int subjectIndex) {
	// 统计学生数量
	int numStudents = 0;
	struct Student *temp = head;
	while (temp != NULL) {
		numStudents++;
		temp = temp->next;
	}

	// 创建数组来存储学生
	struct Student **studentsArray = (struct Student **)malloc(numStudents * sizeof(struct Student *));
	if (studentsArray == NULL) {
		printf("内存分配失败。\n");
		exit(1);
	}

	// 用学生填充数组
	temp = head;
	for (int i = 0; i < numStudents; i++) {
		studentsArray[i] = temp;
		temp = temp->next;
	}

	// 根据科目成绩对数组进行排序
	for (int i = 0; i < numStudents - 1; i++) {
		for (int j = 0; j < numStudents - i - 1; j++) {
			if (getSubjectGrade(studentsArray[j], subjectIndex) < getSubjectGrade(studentsArray[j + 1], subjectIndex)) {
				// 交换学生位置
				struct Student *tempStudent = studentsArray[j];
				studentsArray[j] = studentsArray[j + 1];
				studentsArray[j + 1] = tempStudent;
			}
		}
	}

	// 输出排名信息
	printf("排名\t学号\t姓名\t总分\n");
	for (int i = 0; i < numStudents; i++) {
		struct Student *student = studentsArray[i];
		printf("%d\t%d\t%s\t%.2f\n", i + 1, student->studentID, student->studentName,
		       getSubjectGrade(student, subjectIndex));
	}

	// 释放数组内存
	free(studentsArray);
}

第九部分输出学生的排名奖状

这部分的函数形式其实和输出学生成绩到文件的函数形式是差不多的,也是创建一个文本文档来输出,只不过这部分功能函数多加了一个排名函数而已。

根据总成绩排名生成奖状并导出到文档

// 根据总成绩排名生成奖状并导出到文本文件
void generateCertificates(struct Student *head) {
	// 统计学生数量
	int numStudents = 0;
	struct Student *temp = head;
	while (temp != NULL) {
		numStudents++;
		temp = temp->next;
	}

	// 创建数组来存储学生
	struct Student **studentsArray = (struct Student **)malloc(numStudents * sizeof(struct Student *));
	if (studentsArray == NULL) {
		printf("内存分配失败。\n");
		exit(1);
	}

	// 用学生填充数组
	temp = head;
	for (int i = 0; i < numStudents; i++) {
		studentsArray[i] = temp;
		temp = temp->next;
	}

	// 根据总成绩对数组进行排序
	for (int i = 0; i < numStudents - 1; i++) {
		for (int j = 0; j < numStudents - i - 1; j++) {
			if ((studentsArray[j]->mathGrade + studentsArray[j]->englishGrade +
			        studentsArray[j]->dataStructureGrade + studentsArray[j]->computerOrganizationGrade +
			        studentsArray[j]->probabilityGrade + studentsArray[j]->physicsGrade) <
			        (studentsArray[j + 1]->mathGrade + studentsArray[j + 1]->englishGrade +
			         studentsArray[j + 1]->dataStructureGrade + studentsArray[j + 1]->computerOrganizationGrade +
			         studentsArray[j + 1]->probabilityGrade + studentsArray[j + 1]->physicsGrade)) {
				// 交换学生位置
				struct Student *tempStudent = studentsArray[j];
				studentsArray[j] = studentsArray[j + 1];
				studentsArray[j + 1] = tempStudent;
			}
		}
	}

	// 生成奖状并导出到文本文件
	const char *certificateFilename = "certificates.txt";
	FILE *certificateFile = fopen(certificateFilename, "w");
	if (certificateFile == NULL) {
		printf("无法打开文件 %s\n", certificateFilename);
		exit(1);
	}

	fprintf(certificateFile, "学生成绩奖状\n\n");

	for (int i = 0; i < numStudents; i++) {
		struct Student *student = studentsArray[i];
		fprintf(certificateFile, "第%d名:\n", i + 1);
		fprintf(certificateFile, "恭喜%s同学获得第%d名!\n", student->studentName, i + 1);
		fprintf(certificateFile, "\n");
	}

	fclose(certificateFile);

	printf("奖状已导出到 %s\n", certificateFilename);

	// 释放数组内存
	free(studentsArray);
}

将奖状信息写入文本文档

// 将奖状信息写入文本文件
void writeCertificateToFile(const char *filename, struct Student *student, int rank) {
	FILE *file = fopen(filename, "a");
	if (file == NULL) {
		printf("无法打开文件 %s\n", filename);
		exit(1);
	}

	fprintf(file, "恭喜%s同学获得第%d名!\n", student->studentName, rank);

	fclose(file);
}

接下来是一些增加的辅助函数

在我们这个系统,还有一些小函数来辅助我们完善这个系统,例如说在你选择功能函数的界面,如果你不想使用了,你可以直接输入“exit”来退出,那么这样就会直接结束终端。为了要这个退出的功能,我的定义类型也定义成了“char target[10];”所以我在switch函数里面的参数才会是“target[0]”。

退出函数

// 用户输入是否为 "exit"
int isExitCommand(const char *input) {
	return strcmp(input, "exit") == 0;
}

在我们进入系统之前,我在这个系统里面写了一个密码系统,虽然密码是直接在函数里面定义的(因为C语言没有库,无法存储密码,所以也只能这样做了),在你输入密码的时候,我写了一个“保密”函数,意思就是说你是看不到你输入的密码的,你只能看到“*”,是不是有那种保密的意思了。输入错误之后可以重新输入,也可以输入“exit”来退出系统。在你进入系统之前,都会询问你是否要进入系统,如果选了否也会直接退出系统的。

密码函数

// 定义密码
#define PASSWORD "123456"

// 验证密码
int authenticate() {
	char inputPassword[50];
	char ch;
	int i = 0;

	while (1) {
		printf("请输入密码 (输入 'exit' 以退出): ");

		// 使用 _getch() 获取字符,且不回显
		while ((ch = _getch()) != '\r' && i < sizeof(inputPassword) - 1) {

			// 回车键表示输入结束

			if (ch == '\b' && i > 0) {
				// 处理退格键
				printf("\b \b");
				i--;

			} else {
				// 显示 *,并将字符存入密码数组
				printf("*");
				inputPassword[i++] = ch;
			}
		}

		// 在末尾添加字符串结束符
		inputPassword[i] = '\0';

		// 换行
		printf("\n");

		// 验证密码
		if (strcmp(inputPassword, PASSWORD) == 0) {
			printf("密码正确\n");
			return 1; // 密码正确,退出循环
		} else if (strcmp(inputPassword, "exit") == 0) {
			printf("退出程序\n");
			exit(1); // 用户输入 'exit',退出程序
		} else {
			printf("密码错误,请重新输入。\n");
			i = 0; // 重置计数器,允许重新输入密码
		}
	}
}

系统验证函数

void clearScreen() {
	system("cls");
}

//选择是否进入系统
void Start(char x) {
	// 清空输入缓冲区
	while (getchar() != '\n');

	while (1) {
		switch (x) {
			case 'y':

				if (!authenticate()) {
					printf("密码错误,退出程序。\n");
					exit(1);
				}
				printf("欢迎进入\n");
				clearScreen();
				return ;
			case 'n':
				printf("退出\n");
				exit(1);
			default:
				printf("输入的格式不对,请重新输入(y/n):");
				scanf(" %c", &x);  // 注意空格,避免读取上一次输入的换行符
		}
	}
}

UI界面

作为一个学生成绩管理系统,一个好看的UI界面是至关重要的,可惜的是,我并没有做出一个很好看的UI界面,这部分只能交给读者自行发挥了,我只能做到一个丑丑的UI界面来对付对付了,本人的想象力有限,无得办法了。

UI

void UI(void) {
	const int UI_WIDTH = 70;

	printPikachu();

	printf("\n");
	printStars(UI_WIDTH);
	printf("\n");

	printCenteredText("欢迎使用学生成绩管理系统", UI_WIDTH);

	printStars(UI_WIDTH);
	printf("\n\n");
//	Cat();
	printf("请选择是否进入系统(y/n):");
}

Cat

其实我还有一个很丑的猫头鹰UI,这个我觉得不是很好看,所以注释掉了,如果有读者觉得好看,可以自行取走。这个不是必要的函数。

void Cat(void) {
	printf("        __/\\_/\\__\n");
	printf("        ( o     o )\n");
	printf("        {    \"    }\n");
	printf("        /   @   \\ \n");
	printf("       / |  \"  | \\ \n");
	printf("      (  |     |  )\n");
	printf("     <__/     \\__>\n");
}

主函数

主函数的内容其实很少了,因为我们将那些要用的功能都封装成函数了,如果读者可以将整个文件的函数都按一定的规则将他们分为一个一个的外部文件,那么这个代码就会更加简洁易懂了,你写起来也更加的方便。在主函数里面,在初始化的时候我直接插入了几个学生的信息到链表里面,使用的是尾插法,为什么一下子用头插法,一下又用尾插法呢,我一开始主要还是觉得头插法和尾插法都差不多,我只是用来练练手的,写多了也无所谓。

main

int main() {
	char x;

	//设置界面颜色为绿色
//	system("color 0A") ;

	UI();
	scanf("%c", &x);
	Start(x);

	insertStudent(&head, createStudent(1, "张三", 90.5, 85.0, 78.5, 92.0, 88.0, 75.5));
	insertStudent(&head, createStudent(2, "李四", 88.0, 78.0, 85.5, 90.5, 82.5, 79.0));
	insertStudent(&head, createStudent(3, "王五", 92.5, 89.0, 95.0, 88.5, 91.0, 87.5));

	while (1) {

		Function();
	}

	freeList(&head);
	return 0;
}

结语

在我的这个系统里面,有很多是还未完善的,有些地方可能还会有bug,比如说有些功能函数在输入的时候,这时候你不想用了,你输入一个“exit”之后,可能会导致出bug或者直接一直弹学生的信息出来,这些bug如果读者感兴趣也可以自行修改,这是我在调试的时候发现的。这个系统也还可以加挺多功能的,比如说你可以加科任老师到每一个对应的科目里面,也可以增加一个给学生分配科任老师的功能。本系统使用的链表拓展性很强,所以更多的功能也请读者自行发挥。

完整系统函数

这个系统作者亲测可用,读者可直接CV,但是功能好不好用取决于读者如何看待了。如果不想直接CV,也可以评论或直接私信作者,我可以直接把原文件发给你。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
//#include <conio.h>

// 定义学生结构体
//为什么使用结构体
// 结构体允许将相关的数据项组织在一起,形成一个逻辑上的单元
//结构体提高了代码的可读性。通过给每个数据项起一个有意义的名字,代码读起来更加清晰,容易理解。
struct Student {
	//学生的学号
	int studentID;
	char studentName[50];
	float mathGrade;	//数学
	float englishGrade;	//英语
	float dataStructureGrade;	//数据结构
	float computerOrganizationGrade;	//计算机组成原理
	float probabilityGrade;		//概率论
	float physicsGrade;			//物理
	float total; 		// 添加总成绩字段
	struct Student *next;
};

//链表判空操作
int isListEmpty(struct Student *head) {
	return (head == NULL);
}

// 创建新学生节点
//函数的参数:
//id:新学生的学号 name:新学生的姓名
//其余为新学生的各科成绩
struct Student *createStudent(int id, const char *name, float math, float english, float ds, float co,

                              float probability, float physics) {

	//malloc,给新的学生分配新的节点开辟一个
	//sizeof,计算一个结点所占空间的大小
	struct Student *newStudent = (struct Student *)malloc(sizeof(struct Student));


	//由于系统资源有限,分配可能会失败。为了确保程序能够处理这种情况
	//需要对内存的分配进行一次检查
	if (newStudent == NULL) {
		printf("内存分配失败。\n");
		exit(1);
	}

	//新学生的学号
	newStudent->studentID = id;

	//新学生的姓名
	snprintf(newStudent->studentName, sizeof(newStudent->studentName), "%s", name);

	//高数的成绩
	newStudent->mathGrade = math;

	//英语的成绩
	newStudent->englishGrade = english;

	//数据结构的成绩
	newStudent->dataStructureGrade = ds;

	//计算机组成原理的成绩
	newStudent->computerOrganizationGrade = co;

	//概率论的成绩
	newStudent->probabilityGrade = probability;

	//大物的成绩
	newStudent->physicsGrade = physics;

	//将下一个节点的指针设置为空指针,因为目前没有下一个节点
	newStudent->next = NULL;

	//返回指向新学生节点的指针
	return newStudent;
}

// 插入学生节点到链表末尾
void insertStudent(struct Student **head, struct Student *newStudent) {

	if (*head == NULL) {
		*head = newStudent;

	} else {
		struct Student *temp = *head;
		while (temp->next != NULL) {
			temp = temp->next;
		}
		//将新学生结点插入
		temp->next = newStudent;
	}
}

// 根据学号删除学生节点
void deleteStudent(struct Student **head, int id) {
	struct Student *current = *head, *prev = NULL;

	//遍历成功,删除结点
	if (current != NULL && current->studentID == id) {
		*head = current->next;
		free(current);
		return;
	}

	//学号匹配,一直遍历
	while (current != NULL && current->studentID != id) {
		prev = current;
		current = current->next;
	}

	if (current == NULL) {
		printf("未找到学号为 %d 的学生。\n", id);
		return;
	}

	//如果找到匹配的,删除当前结点
	prev->next = current->next;
	free(current);
}

// 计算学生总成绩
float calculateTotalGrade(struct Student *student) {
	return student->mathGrade +
	       student->englishGrade +
	       student->dataStructureGrade +
	       student->computerOrganizationGrade +
	       student->probabilityGrade +
	       student->physicsGrade;
}

// 打印所有学生信息
void printStudents(struct Student *head) {
	struct Student *temp = head;
	float total = 0.0;
	while (temp != NULL) {
		// 计算总成绩
		total = calculateTotalGrade(temp);

		// 打印学生信息
		printf("学号:%d, 姓名:%s, 数学:%.2f, 英语:%.2f, 数据结构:%.2f, 计算机组成原理:%.2f, 概率论:%.2f, 物理:%.2f, 总成绩:%.2f\n",
		       temp->studentID, temp->studentName, temp->mathGrade, temp->englishGrade, temp->dataStructureGrade,
		       temp->computerOrganizationGrade, temp->probabilityGrade, temp->physicsGrade, total);
		temp = temp->next;
	}
}

// 打印指定学生信息
int printStudent(struct Student *head, int targetID) {
	struct Student *temp = head;
	float total = 0.0;
	while (temp != NULL) {

		//学号匹配

		if (temp->studentID == targetID) {
			// 计算总成绩
			total = calculateTotalGrade(temp);

			// 打印学生信息
			printf("学号:%d, 姓名:%s, 数学:%.2f, 英语:%.2f, 数据结构:%.2f, 计算机组成原理:%.2f, 概率论:%.2f, 物理:%.2f, 总成绩:%.2f\n",
			       temp->studentID, temp->studentName, temp->mathGrade, temp->englishGrade, temp->dataStructureGrade,
			       temp->computerOrganizationGrade, temp->probabilityGrade, temp->physicsGrade, total);
			return 1;  // 找到学号匹配的学生后直接返回,不需要继续遍历
		}
		//如果当前学号不匹配,移动到下一个结点
		temp = temp->next;
	}
	printf("未找到学号为%d的学生。\n", targetID);
	return 0;
}

//输出对应学号的学生到文本文件中
void printStudentToFile(struct Student *head, int studentID, const char *filename) {
	//打开为写模式
	FILE *file = fopen(filename, "w");
	if (file == NULL) {
		printf("无法打开文件 %s\n", filename);
		exit(1);
	}

	struct Student *temp = head;
	while (temp != NULL) {

		if (temp->studentID == studentID) {
			//写入文件
			fprintf(file, "学号\t姓名\t数学\t英语\t数据结构\t计算机组成原理\t概率论\t物理\n");
			fprintf(file, "%d\t%s\t%.2f\t%.2f\t%.2f\t %.2f\t        %.2f  \t%.2f\n",
			        temp->studentID, temp->studentName, temp->mathGrade, temp->englishGrade,
			        temp->dataStructureGrade, temp->computerOrganizationGrade, temp->probabilityGrade,
			        temp->physicsGrade);
			fclose(file);	//关闭文件
			return;
		}
		temp = temp->next;
	}

	// 学号不存在
	printf("找不到学号为 %d 的学生。\n", studentID);
	fclose(file);
}

// 打印所有学生信息到文件
void printStudentsToFile(struct Student *head, const char *filename) {
	FILE *file = fopen(filename, "w");
	if (file == NULL) {
		printf("无法打开文件 %s\n", filename);
		exit(1);
	}

	fprintf(file, "学号\t姓名\t数学\t英语\t数据结构\t计算机组成原理\t概率论\t物理\n");
	struct Student *temp = head;
	while (temp != NULL) {
		fprintf(file, "%d\t%s\t%.2f\t%.2f\t%.2f\t %.2f\t        %.2f  \t%.2f\n",
		        temp->studentID, temp->studentName, temp->mathGrade, temp->englishGrade,
		        temp->dataStructureGrade, temp->computerOrganizationGrade, temp->probabilityGrade,
		        temp->physicsGrade);
		temp = temp->next;
	}

	fclose(file);
}

// 释放链表内存
void freeList(struct Student **head) {
	struct Student *current = *head;
	struct Student *next;

	while (current != NULL) {
		next = current->next;

		//释放当前链表
		free(current);

		current = next;
	}

	*head = NULL;
}

void printStars(int count) {
	for (int i = 0; i < count; ++i) {
		printf("*");
	}
}

void printCenteredText(const char *text, int width) {
	int padding = (width - strlen(text)) / 2;
	printf("%*s%s%*s\n", padding, "", text, padding, "");
}

void printPikachuFace() {
	printf("   *****   \n");
	printf("  *******  \n");
	printf(" ********* \n");
	printf("***********\n");
	printf("***********\n");
	printf(" ********* \n");
	printf("  *******  \n");
	printf("   *****   \n");
}

void printPikachuEars() {
	printf("   ** **   \n");
	printf("  **   **  \n");
	printf(" **     ** \n");
	printf("**       **\n");
}

void printPikachu() {
	printPikachuFace();
	printPikachuEars();
}

// 定义密码
#define PASSWORD "123456"

// 验证密码
int authenticate() {
	char inputPassword[50];
	char ch;
	int i = 0;

	while (1) {
		printf("请输入密码 (输入 'exit' 以退出): ");

		// 使用 _getch() 获取字符,且不回显
		while ((ch = _getch()) != '\r' && i < sizeof(inputPassword) - 1) {

			// 回车键表示输入结束

			if (ch == '\b' && i > 0) {
				// 处理退格键
				printf("\b \b");
				i--;

			} else {
				// 显示 *,并将字符存入密码数组
				printf("*");
				inputPassword[i++] = ch;
			}
		}

		// 在末尾添加字符串结束符
		inputPassword[i] = '\0';

		// 换行
		printf("\n");

		// 验证密码
		if (strcmp(inputPassword, PASSWORD) == 0) {
			printf("密码正确\n");
			return 1; // 密码正确,退出循环
		} else if (strcmp(inputPassword, "exit") == 0) {
			printf("退出程序\n");
			exit(1); // 用户输入 'exit',退出程序
		} else {
			printf("密码错误,请重新输入。\n");
			i = 0; // 重置计数器,允许重新输入密码
		}
	}
}

void clearScreen() {
	system("cls");
}

//选择是否进入系统
void Start(char x) {
	// 清空输入缓冲区
	while (getchar() != '\n');

	while (1) {
		switch (x) {
			case 'y':

				if (!authenticate()) {
					printf("密码错误,退出程序。\n");
					exit(1);
				}
				printf("欢迎进入\n");
				clearScreen();
				return ;
			case 'n':
				printf("退出\n");
				exit(1);
			default:
				printf("输入的格式不对,请重新输入(y/n):");
				scanf(" %c", &x);  // 注意空格,避免读取上一次输入的换行符
		}
	}
}

struct Student *head = NULL;

// 检查学号是否重复
int isStudentIDDuplicate(struct Student *head, int studentID) {
	struct Student *temp = head;
	while (temp != NULL) {
		if (temp->studentID == studentID) {
			return 1; // 学号重复
		}
		temp = temp->next;
	}
	return 0; // 学号不重复
}

// 输入学生信息并返回学生节点指针
struct Student *inputStudent() {
	struct Student *newStudent = createStudent(0, "", 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);

	//
	int flag;
	do {
		int studentID;
		printf("请输入学生学号:");
		scanf("%d", &studentID);

		// 检查学号是否重复
		if (isStudentIDDuplicate(head, studentID)) {
			printf("学号重复,检查是否已插入。\n");
			printf("请选择下一步:\n");
			printf("1.继续插入  2.打印所有学生的信息\n");

			scanf("%d", &flag);

			if (flag == 1) {
				continue; // 如果学号重复,则重新输入

			} else if (flag == 2) {
				// 打印所有学生信息
				printf("\n所有学生信息:\n");
				printStudents(head);
				printf("\n");
				continue;
			}
		}

		newStudent = createStudent(studentID, "", 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
		break; // 学号不重复,退出循环
	} while (1);

	printf("请输入学生姓名:");
	scanf("%s", newStudent->studentName);

	printf("请输入数学成绩:");
	scanf("%f", &(newStudent->mathGrade));

	printf("请输入英语成绩:");
	scanf("%f", &(newStudent->englishGrade));

	printf("请输入数据结构与算法成绩:");
	scanf("%f", &(newStudent->dataStructureGrade));

	printf("请输入计算机组成原理成绩:");
	scanf("%f", &(newStudent->computerOrganizationGrade));

	printf("请输入概率论与数理统计成绩:");
	scanf("%f", &(newStudent->probabilityGrade));

	printf("请输入大学物理成绩:");
	scanf("%f", &(newStudent->physicsGrade));

	newStudent->next = NULL;

	return newStudent;
}

// 将学生加入链表
struct Student *addToLinkedList(struct Student *head, struct Student *newStudent) {
	if (newStudent != NULL) {
		newStudent->next = head;
		head = newStudent;
	}
	return head;
}

// 打印指定学生信息及其平均分
void printStudentWithAverage(struct Student *head, int targetID) {
	struct Student *temp = head;
	float total = 0.0;
	int subjectCount = 0;

	while (temp != NULL) {
		if (temp->studentID == targetID) {
			// 计算总成绩
			total = calculateTotalGrade(temp);

			// 打印学生信息
			printf("学号:%d, 姓名:%s, 数学:%.2f, 英语:%.2f, 数据结构:%.2f, 计算机组成原理:%.2f, 概率论:%.2f, 物理:%.2f, 总成绩:%.2f\n",
			       temp->studentID, temp->studentName, temp->mathGrade, temp->englishGrade, temp->dataStructureGrade,
			       temp->computerOrganizationGrade, temp->probabilityGrade, temp->physicsGrade, total);
			subjectCount++;
			break;  // 找到学生后直接退出循环
		}
		temp = temp->next;
	}

	if (subjectCount > 0) {
		float average = total / 6.0;  // 每个学生有六门课程
		printf("该学生总成绩平均分:%.2f\n", average);
	} else {
		printf("未找到学号为 %d 的学生。\n", targetID);
	}
}

// 打印所有学生信息及总成绩平均分
void printStudentsTotalAverage(struct Student *head) {
	struct Student *temp = head;
	float totalSum = 0.0;
	int studentCount = 0;

	while (temp != NULL) {
		// 计算总成绩
		float total = calculateTotalGrade(temp);
		float average = total / 6;
		totalSum += total;

		// 打印学生信息和总成绩
		printf("学号:%d, 姓名:%s, 数学:%.2f, 英语:%.2f, 数据结构:%.2f, 计算机组成原理:%.2f, 概率论:%.2f, 物理:%.2f, 总成绩:%.2f,平均分:%.2f\n",
		       temp->studentID, temp->studentName, temp->mathGrade, temp->englishGrade, temp->dataStructureGrade,
		       temp->computerOrganizationGrade, temp->probabilityGrade, temp->physicsGrade, total, average);

		temp = temp->next;
		studentCount++;
	}

	if (studentCount > 0) {
		float average = totalSum / studentCount;
		printf("\n所有学生总成绩平均分:%.2f\n", average);
	} else {
		printf("\n暂无学生信息。\n");
	}
}

// 从外部文本文档读取学生信息并录入
void loadStudentsFromFile(const char *filename, struct Student **head) {
	setlocale(LC_ALL, "chs");
	FILE *file = fopen(filename, "r");
	if (file == NULL) {
		printf("无法打开文件 %s\n", filename);
		exit(1);
	}

	int studentID;
	char studentName[50];
	float mathGrade, englishGrade, dsGrade, coGrade, probabilityGrade, physicsGrade;

	while (fscanf(file, "%d %49s %f %f %f %f %f %f",
	              &studentID, studentName, &mathGrade, &englishGrade, &dsGrade, &coGrade, &probabilityGrade, &physicsGrade) == 8) {
		struct Student *newStudent = (struct Student *)malloc(sizeof(struct Student));
		if (newStudent == NULL) {
			printf("内存分配失败。\n");
			exit(1);
		}

		newStudent->studentID = studentID;
		snprintf(newStudent->studentName, sizeof(newStudent->studentName), "%s", studentName);
		newStudent->mathGrade = mathGrade;
		newStudent->englishGrade = englishGrade;
		newStudent->dataStructureGrade = dsGrade;
		newStudent->computerOrganizationGrade = coGrade;
		newStudent->probabilityGrade = probabilityGrade;
		newStudent->physicsGrade = physicsGrade;
		newStudent->next = NULL;

//		 检查学号是否重复
		if (isStudentIDDuplicate(*head, studentID)) {
			printf("\n");
			printf("警告:学号为 %d 的学生信息重复!\n", studentID);
			printf("重复的学生信息:\n");
			printStudent(newStudent, studentID);
			printf("\n");
		}

		// 将新学生添加到链表
		if (*head == NULL) {
			*head = newStudent;
		} else {
			struct Student *temp = *head;
			while (temp->next != NULL) {
				temp = temp->next;
			}
			temp->next = newStudent;
		}



	}

	fclose(file);
}

// 以总分从大到小的顺序对学生进行排名
void rankStudentsByTotal(struct Student *head) {
	// 计算学生总分
	struct Student *temp = head;
	while (temp != NULL) {
		// 将各科成绩相加赋值给 total 字段
		temp->total = temp->mathGrade + temp->englishGrade +
		              temp->dataStructureGrade + temp->computerOrganizationGrade +
		              temp->probabilityGrade + temp->physicsGrade;
		temp = temp->next;
	}

	// 将学生信息放入数组
	int numStudents = 0;
	temp = head;
	while (temp != NULL) {
		numStudents++;
		temp = temp->next;
	}

	struct Student **studentsArray = (struct Student **)malloc(numStudents * sizeof(struct Student *));
	if (studentsArray == NULL) {
		printf("内存分配失败。\n");
		exit(1);
	}

	int i = 0;
	temp = head;
	while (temp != NULL) {
		studentsArray[i] = temp;
		i++;
		temp = temp->next;
	}

	// 冒泡排序,按总分从大到小排列
	for (int j = 0; j < numStudents - 1; j++) {
		for (int k = 0; k < numStudents - j - 1; k++) {
			if (studentsArray[k]->total < studentsArray[k + 1]->total) {
				// 交换两个学生的位置
				struct Student *tempStudent = studentsArray[k];
				studentsArray[k] = studentsArray[k + 1];
				studentsArray[k + 1] = tempStudent;
			}
		}
	}

	// 输出排名信息
	printf("排名\t学号\t姓名\t数学\t英语\t数据结构\t计算机组成原理\t概率论\t物理\t总分\n");
	for (int j = 0; j < numStudents; j++) {
		printf("%d\t%d\t%s\t%.2f\t%.2f\t%.2f\t\t%.2f\t\t%.2f\t%.2f\t%.2f\n", j + 1, studentsArray[j]->studentID,
		       studentsArray[j]->studentName, studentsArray[j]->mathGrade, studentsArray[j]->englishGrade,
		       studentsArray[j]->dataStructureGrade, studentsArray[j]->computerOrganizationGrade,
		       studentsArray[j]->probabilityGrade, studentsArray[j]->physicsGrade, studentsArray[j]->total);
	}
	// 释放内存
	free(studentsArray);
}

// 获取特定科目的成绩
float getSubjectGrade(struct Student *student, int subjectIndex) {
	switch (subjectIndex) {
		case 0:
			return student->mathGrade;
		case 1:
			return student->englishGrade;
		case 2:
			return student->dataStructureGrade;
		case 3:
			return student->computerOrganizationGrade;
		case 4:
			return student->probabilityGrade;
		case 5:
			return student->physicsGrade;
		default:
			return 0.0;
	}
}

// 按照特定科目排名学生
void rankStudentsBySubject(struct Student *head, int subjectIndex) {
	// 统计学生数量
	int numStudents = 0;
	struct Student *temp = head;
	while (temp != NULL) {
		numStudents++;
		temp = temp->next;
	}

	// 创建数组来存储学生
	struct Student **studentsArray = (struct Student **)malloc(numStudents * sizeof(struct Student *));
	if (studentsArray == NULL) {
		printf("内存分配失败。\n");
		exit(1);
	}

	// 用学生填充数组
	temp = head;
	for (int i = 0; i < numStudents; i++) {
		studentsArray[i] = temp;
		temp = temp->next;
	}

	// 根据科目成绩对数组进行排序
	for (int i = 0; i < numStudents - 1; i++) {
		for (int j = 0; j < numStudents - i - 1; j++) {
			if (getSubjectGrade(studentsArray[j], subjectIndex) < getSubjectGrade(studentsArray[j + 1], subjectIndex)) {
				// 交换学生位置
				struct Student *tempStudent = studentsArray[j];
				studentsArray[j] = studentsArray[j + 1];
				studentsArray[j + 1] = tempStudent;
			}
		}
	}

	// 输出排名信息
	printf("排名\t学号\t姓名\t总分\n");
	for (int i = 0; i < numStudents; i++) {
		struct Student *student = studentsArray[i];
		printf("%d\t%d\t%s\t%.2f\n", i + 1, student->studentID, student->studentName,
		       getSubjectGrade(student, subjectIndex));
	}

	// 释放数组内存
	free(studentsArray);
}

// 将奖状信息写入文本文件
void writeCertificateToFile(const char *filename, struct Student *student, int rank) {
	FILE *file = fopen(filename, "a");
	if (file == NULL) {
		printf("无法打开文件 %s\n", filename);
		exit(1);
	}

	fprintf(file, "恭喜%s同学获得第%d名!\n", student->studentName, rank);

	fclose(file);
}

// 根据总成绩排名生成奖状并导出到文本文件
void generateCertificates(struct Student *head) {
	// 统计学生数量
	int numStudents = 0;
	struct Student *temp = head;
	while (temp != NULL) {
		numStudents++;
		temp = temp->next;
	}

	// 创建数组来存储学生
	struct Student **studentsArray = (struct Student **)malloc(numStudents * sizeof(struct Student *));
	if (studentsArray == NULL) {
		printf("内存分配失败。\n");
		exit(1);
	}

	// 用学生填充数组
	temp = head;
	for (int i = 0; i < numStudents; i++) {
		studentsArray[i] = temp;
		temp = temp->next;
	}

	// 根据总成绩对数组进行排序
	for (int i = 0; i < numStudents - 1; i++) {
		for (int j = 0; j < numStudents - i - 1; j++) {
			if ((studentsArray[j]->mathGrade + studentsArray[j]->englishGrade +
			        studentsArray[j]->dataStructureGrade + studentsArray[j]->computerOrganizationGrade +
			        studentsArray[j]->probabilityGrade + studentsArray[j]->physicsGrade) <
			        (studentsArray[j + 1]->mathGrade + studentsArray[j + 1]->englishGrade +
			         studentsArray[j + 1]->dataStructureGrade + studentsArray[j + 1]->computerOrganizationGrade +
			         studentsArray[j + 1]->probabilityGrade + studentsArray[j + 1]->physicsGrade)) {
				// 交换学生位置
				struct Student *tempStudent = studentsArray[j];
				studentsArray[j] = studentsArray[j + 1];
				studentsArray[j + 1] = tempStudent;
			}
		}
	}

	// 生成奖状并导出到文本文件
	const char *certificateFilename = "certificates.txt";
	FILE *certificateFile = fopen(certificateFilename, "w");
	if (certificateFile == NULL) {
		printf("无法打开文件 %s\n", certificateFilename);
		exit(1);
	}

	fprintf(certificateFile, "学生成绩奖状\n\n");

	for (int i = 0; i < numStudents; i++) {
		struct Student *student = studentsArray[i];
		fprintf(certificateFile, "第%d名:\n", i + 1);
		fprintf(certificateFile, "恭喜%s同学获得第%d名!\n", student->studentName, i + 1);
		fprintf(certificateFile, "\n");
	}

	fclose(certificateFile);

	printf("奖状已导出到 %s\n", certificateFilename);

	// 释放数组内存
	free(studentsArray);
}

// 用户输入是否为 "exit"
int isExitCommand(const char *input) {
	return strcmp(input, "exit") == 0;
}

// 等待回车的函数
void waitForEnter() {
	printf("按回车键以继续.......\n");
	while (getchar() != '\n');  // 消耗输入缓冲区中的回车符
	getchar();  // 等待用户按回车
}

void Function(void) {
	char target[10];
	int flag1, flag2;
	struct Student *newStudent;
	int flag3, flag4, flag5, flag6, flag7;
	int subjectIndex;

//	printf("\n请选择下一步操作(输入exit退出程序):\n");
//	printf("   1.查询学生信息     2.插入学生信息    3.删除信息     4.输出所有学生成绩到文件中     5.打印全部学生的信息\n");
//	printf("   6.查询学生平均成绩 7.从外部文件读取学生的信息       8.输出总成绩排名  \n");
//	printf("   9.输出排名奖状到文件中\n");
	printf("\n请选择下一步操作(输入 exit 退出程序):\n");
	printf("1. 查询学生信息           2. 插入学生信息\n");
	printf("3. 删除信息               4. 输出所有学生成绩到文件中\n");
	printf("5. 打印全部学生的信息     6. 查询学生平均成绩\n");
	printf("7. 从外部文件读取学生信息 8. 输出总成绩排名\n");
	printf("9. 输出排名奖状到文件中\n");
	scanf("%s", target);

	if (isExitCommand(target)) {
		printf("退出程序。\n");
		exit(1);
	}
	clearScreen();

	switch (target[0]) {
		case '1':
			printf("现在进行查询:\n");
			printf("请输入要查询的学号:");
			scanf("%d", &flag1);
			if (printStudent(head, flag1)) {
				printf("请问是否需要将学生的成绩输出到文件中\n");
				printf("1.是         2.否\n");
				scanf("%d", &flag4);

				if (flag4 == 1) {
					printStudentToFile(head, flag1, "student_grades.txt");
					printf("输出完成\n");
				}
			}
			if (isListEmpty(head)) {
				printf("目前系统内并无学生信息");
			}
			waitForEnter();
			break;
		case '2':
			printf("现在进行学生信息的输入\n");
			newStudent = inputStudent();
			head = addToLinkedList(head, newStudent);
			printf("输入完成");
			waitForEnter();
			break;
		case '3':
			printf("现在进行学生信息的删除\n");
			printf("请问是否需要打印全部学生的信息:\n");
			printf("1.是      2.否\n");
			scanf("%d", &flag3);

			if (flag3 == 1) {
				printf("\n所有学生信息:\n");
				printStudents(head);
			}

			printf("请输入要删除学生的学号:");
			scanf("%d", &flag2);
			deleteStudent(&head, flag2);
			waitForEnter();
			break;
		case '4':
			printStudentsToFile(head, "Students_Graph.txt");
			printf("输出完成\n");
			waitForEnter();
			break;
		case '5':
			printStudents(head);
			waitForEnter();
			break;
		case '6':
			printf("是否输出全部学生的总平均成绩:\n");
			printf("1.是         2.否\n");
			scanf("%d", &flag5);
			if (flag5 == 1) {
				printStudentsTotalAverage(head);
				waitForEnter();
				break;
			} else {
				printf("请输入要输出学生的学号:");
				scanf("%d", &flag6);
				printStudentWithAverage(head, flag6);
				waitForEnter();
				break;
			}
		case '7':
			loadStudentsFromFile("student_info.txt", &head);
			printf("读取完成\n");
			waitForEnter();
			break;
		case '8':
			printf("按成绩从大到小的排名为:\n");
			rankStudentsByTotal(head);
			printf("是否需要输出单个科目的排名\n");
			printf("1.是        2.否\n");
			scanf("%d", &flag7);
			if (flag7 == 1) {
				printf("请选择科目(1.数学 2.英语 3.数据结构 4.计算机组成原理 5.概率论 6.物理): ");
				scanf("%d", &subjectIndex);

				if (subjectIndex < 1 || subjectIndex > 6) {
					printf("无效的科目选择。\n");
					waitForEnter();
					break;

				} else {
					rankStudentsBySubject(head, subjectIndex - 1);
					waitForEnter();
					break;
				}
			} else {
				waitForEnter();
				break;
			}
		case '9':
			generateCertificates(head);
			printf("输出完成\n");
			waitForEnter();
			break;
		default :
			printf("输入有误,请重新输入");
			waitForEnter();
			break;
	}
	clearScreen();
}

void UI(void) {
	const int UI_WIDTH = 70;

	printPikachu();

	printf("\n");
	printStars(UI_WIDTH);
	printf("\n");

	printCenteredText("欢迎使用学生成绩管理系统", UI_WIDTH);

	printStars(UI_WIDTH);
	printf("\n\n");
//	Cat();
	printf("请选择是否进入系统(y/n):");
}

void Cat(void) {
	printf("        __/\\_/\\__\n");
	printf("        ( o     o )\n");
	printf("        {    \"    }\n");
	printf("        /   @   \\ \n");
	printf("       / |  \"  | \\ \n");
	printf("      (  |     |  )\n");
	printf("     <__/     \\__>\n");
}

int main() {
//	setlocale(LC_ALL, "chs");
	char x;

	//设置界面颜色为绿色
//	system("color 0A") ;

	UI();
	scanf("%c", &x);
	Start(x);

	insertStudent(&head, createStudent(1, "张三", 90.5, 85.0, 78.5, 92.0, 88.0, 75.5));
	insertStudent(&head, createStudent(2, "李四", 88.0, 78.0, 85.5, 90.5, 82.5, 79.0));
	insertStudent(&head, createStudent(3, "王五", 92.5, 89.0, 95.0, 88.5, 91.0, 87.5));

	while (1) {

		Function();
	}

	freeList(&head);
	return 0;
}

  • 44
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

—你的鼬先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值