很久以前我们软件工程老师用这个项目设计当做我们的期末作业
当时也是设计了很长时间,最后得了很高的分数,当时记得很激动
C已复制
//定义学生数据存储位置,比如我就放到D盘的"数据库.txt"文件里
//TXT用UTF-8格式,编译器也设置为UTF-8打开,不然中文全是乱七八糟的字符
struct Student {
char name[30]; // 姓名
long long id; // 学号
float score; // 成绩
};
//原本的程序输出的时候输出语句都靠的太近了所以代码中加了很多printf("\n");语句
// 为了方便检测学生的学号重叠问题,新定义一个checkIdExists函数检查学号是否已存在
int checkIdExists(struct Student students[], int count, int id) {
for (int i = 0; i < count; i++) {
if (students[i].id == id) {
return 1; // 学号已存在
}
}
return 0; // 学号不存在
}
//输入学生信息函数
void inputStudent(struct Student students[], int *count) {
int newCount;
printf("请问要录入几个学生呢?\n");
printf("人数:");
scanf("%d", &newCount);
// 计算总共会有多少学生
int total = *count + newCount;
//判断输入人数是否合理 ,下面的插入学生函数也做了相同的判断
if (total > MAX_STUDENTS) {
int canAdd = MAX_STUDENTS - *count;
printf("对不起哦,您最多只能再录入 %d 个人(总共不能超过 %d 人)\n", canAdd, MAX_STUDENTS);
// 询问用户是否要继续录入可添加的数量
printf("是否录入 %d 个学生?(1. 是 / 0. 否): ", canAdd);
int choice;
scanf("%d", &choice);
if (choice != 1) {
return;
}
newCount = canAdd;
}
// 从当前学生数开始录入新学生
for (int i = *count; i < *count + newCount; i++) {
printf("\n");
printf("第%d个学生\n", i + 1 - *count);
//这里因为地球上有一堆人同名同姓,所以允许学生名字相同但学号必须唯一
printf("姓名:");
scanf("%s", students[i].name);
//确保学号为10位及以下
while (1) {
printf("学号:");
//先判断学号是否10位及以下
if (scanf("%lld", &students[i].id) != 1 || students[i].id < 0 || students[i].id > 9999999999) {
printf("学号无效(学号必须是最多10位的数字),请重新输入。\n");
while (getchar() != '\n');
} else if (checkIdExists(students, i, students[i].id)) {
//再调用checkIdExists判断学号是否重复
printf("学号 %lld 已存在,请重新输入哦\n", students[i].id);
} else {
//两种情况都不存在的话就跳出while循环
break;
}
}
//确保成绩0~750
while (1) {
printf("成绩:");
//逻辑和 确保学号为10位及以下 一模一样
if (scanf("%f", &students[i].score) != 1 || students[i].score < 0 || students[i].score > 750) {
printf("成绩无效(成绩必须在0到750之间),请重新输入哦。\n");
while (getchar() != '\n');
} else {
break;
}
}
}
// 更新学生总数
*count += newCount;
printf("\n成功录入 %d 名新学生!当前总共 %d 名学生。\n", newCount, *count);
}
//显示所有学生信息
void printAllStudents(struct Student students[], int count) {
//如果没有一个学生
if (count == 0) {
printf("当前系统没有学生哦。\n");
return;
}
//for循环遍历
for (int i = 0; i < count; i++) {
printf("姓名:%s 学号:%lld 成绩:%.2f\n", students[i].name, students[i].id, students[i].score);
}
}
// 查找学生信息函数
void searchStudent(struct Student students[], int count) {
//如果没有一个学生
if (count == 0) {
printf("当前系统没有学生哦。\n");
return;
}
int choice;
printf("请选择查找方式:\n");
printf("1. 按姓名查找\n");
printf("2. 按学号查找\n");
printf("请输入选项:");
scanf("%d", &choice);
switch (choice) {
case 1: {
// 按姓名查找
char name[30];
printf("请输入学生姓名:");
scanf("%s", name);
//使用found来定义学生的存在状态 0不存在,1存在
int found = 0;
for (int i = 0; i < count; i++) {
//用strcmp将学生姓名一一对比,如果找到就跳出循环
if (strcmp(students[i].name, name) == 0) {
printf("\n找到啦!\n");
printf("姓名:%s 学号:%lld 成绩:%.2f\n", students[i].name, students[i].id, students[i].score);
found = 1;
break;
}
}
//找不到就输出找不到
if (!found) {
printf("没有找到该同学诶。\n");
break;
}
break;
}
case 2: {
// 按学号查找
int id;
printf("请输入学生学号:");
scanf("%d", &id);
int found = 0;
for (int i = 0; i < count; i++) {
//和刚才的姓名查找逻辑一模一样,只是把结构体成员由name改为id
if (students[i].id == id) {
printf("\n找到啦!\n");
printf("姓名:%s 学号:%lld 成绩:%.2f\n", students[i].name, students[i].id, students[i].score);
found = 1;
break;
}
}
//没找到就是没找到
if (!found) {
printf("没有找到该同学诶。\n");
break;
}
break;
}
//如果不满足前两个case条件,就输出default
default:
printf("无效的选项,您只能输入1或者2哦。\n");
}
}
// 删除学生信息函数
void deleteStudent(struct Student students[], int *count) {
//如果没有一个学生
if (*count == 0) {
printf("当前系统没有学生哦。\n");
return;
}
int choice;
printf("请选择删除方式:\n");
printf("1. 按姓名删除\n");
printf("2. 按学号删除\n");
printf("请输入选项:");
scanf("%d", &choice);
//这里之所以让index=-1而不是0是因为还有一种情况,就是删除前先要通过姓名和学号来查找学生
//如果输入了一个不存在的名字或学号,就要给出"找不到"的提示
//也就是说,前面的查找函数在这里又用了一下
int index = -1;
switch (choice) {
case 1: {
// 按姓名删除
char name[30];
printf("请输入学生姓名:");
scanf("%s", name);
//删除的逻辑和 上面查找函数一模一样,就是找到这个同学,然后删了他,只是定义了一个index=-1的区别
//因为如果定义index为0或者正数,就会影响下面for循环的使用 ,所以随便定义一个负数比如-1就可以了
for (int i = 0; i < *count; i++) {
if (strcmp(students[i].name, name) == 0) {
//找到了就将index改为当前的学生数组的索引
index = i;
break;
}
}
//找不到就跳出
break;
}
case 2: {
// 按学号删除
int id;
printf("请输入学生学号:");
scanf("%d", &id);
//和姓名删除一模一样
for (int i = 0; i < *count; i++) {
if (students[i].id == id) {
index = i;
break;
}
}
break;
}
//如果输入其他数字就提醒一下
default:
printf("无效的选项,您只能输入1或者2哦。\n");
return;
}
//只要姓名和学号找不到,而代码在函数内从上往下执行 ,就一定会对index的值进行判断
//然后提醒没有这个同学信息
if (index == -1) {
printf("没有找到该同学诶。\n");
return;
}
// 确认删除
int confirm;
for (int i = 0; i < 3; i++) {
//为防止不小心删错了,所以用for循环搞一个确认三遍
// validInput用于检测输入是否有效,因为我发现输入0和1以外的数字会退出删除操作,所以强制规范一下
int validInput = 0;
while (!validInput) {
printf("确定要删除该学生信息吗?需要您确认三遍才行哦(1. 确定 / 0. 取消):");
if (scanf("%d", &confirm) == 1 && (confirm == 0 || confirm == 1)) {
validInput = 1; // 如果输入有效,就是要么是0,要么是1,就退出循环
} else {
printf("无效的输入,您只能输入0或者1哦\n");
while (getchar() != '\n'); // 清空输入缓冲区
}
}
if (confirm != 1) {
printf("删除操作已取消。\n");
return;
}
}
// 删除学生信息
//这里删除之后调整一下学生索引的位置
//就是继承原先数组学生的顺序,因为删了一个,新数组又少一个位子,所以被删的学生的索引位子会被下一个学生顶替
for (int i = index; i < *count - 1; i++) {
students[i] = students[i + 1];
}
//删除后将学生总数减一
(*count)--;
printf("该同学信息已删除!\n");
}
// 修改学生信息函数
void modifyStudent(struct Student students[], int count) {
//如果没有一个学生
if (count == 0) {
printf("当前系统没有学生哦。\n");
return;
}
int choice;
printf("选择修改方式:\n");
printf("1. 按姓名修改\n");
printf("2. 按学号修改\n");
printf("请输入选项:");
scanf("%d", &choice);
//修改的话和删除差不多(指的是前期都是通过按姓名或学号先找到学生,然后修改)
//以下部分就是直接套用上面删除函数的逻辑
int index = -1;
switch (choice) {
case 1: {
// 按姓名修改
char name[30];
printf("请输入要修改的学生的姓名:");
scanf("%s", name);
for (int i = 0; i < count; i++) {
if (strcmp(students[i].name, name) == 0) {
index = i;
break;
}
}
break;
}
case 2: {
// 按学号修改
int id;
printf("请输入要修改的学生的学号:");
scanf("%d", &id);
for (int i = 0; i < count; i++) {
if (students[i].id == id) {
index = i;
break;
}
}
break;
}
default:
printf("无效的选项,您只能输入1或者2哦。\n");
return;
}
if (index == -1) {
printf("没有找到该同学诶。\n");
return;
}
printf("\n找到啦!这是该同学的信息!:\n");
printf("姓名:%s 学号:%lld 成绩:%.2f\n", students[index].name, students[index].id, students[index].score);
//找到后就是修改这个同学的信息
int modifyChoice;
printf("\n请问您要修改他/她的什么信息呢:\n");
printf("1. 修改姓名\n");
printf("2. 修改学号\n");
printf("3. 修改成绩\n");
printf("请输入选项:");
scanf("%d", &modifyChoice);
// 存储原始值,以便在取消修改时恢复
struct Student original = students[index];
//因为不知道到底改什么信息,所以用switch case语句问一下 要改哪个
switch (modifyChoice) {
case 1: {
// 修改姓名
printf("请输入新的姓名:");
scanf("%s", students[index].name);
break;
}
case 2: {
// 修改学号
//和case1逻辑一模一样
printf("请输入新的学号:");
scanf("%lld", &students[index].id);
break;
}
case 3: {
// 修改成绩
//和case1逻辑一模一样
printf("请输入新的成绩:");
scanf("%f", &students[index].score);
break;
}
//就三个选项,要是输入了其他的只能default提醒一下输错了
default:
printf("无效的选项,您只能输入1或者2或者3哦。\n");
return;
}
//同样为防止不小心误改,改之后确认一下
//这里的逻辑和删除函数里确认删不删逻辑一样
// 统一确认操作(只需一次确认)
int confirm;
int validInput = 0;// 用于检测输入是否有效
while (!validInput) {
printf("确定要修改学生信息吗?(1. 确定 / 0. 取消):");
if (scanf("%d", &confirm) == 1 && (confirm == 0 || confirm == 1)) {
validInput = 1;// 如果输入有效,退出循环
} else {
printf("无效的输入,您只能输入0或者1哦\n");
while (getchar() != '\n');// 清空输入缓冲区
}
}
if (confirm != 1) {
// 取消修改,恢复原始值
students[index] = original;
printf("修改操作已取消。\n");
return;
}
printf("学生信息已修改。\n");
printf("这是他/她修改后的学生信息哦:\n");
//这里打印一下学生信息看看有没有改错
printf("姓名:%s 学号:%lld 成绩:%.2f\n", students[index].name, students[index].id, students[index].score);
}
// 插入学生信息函数
void insertStudent(struct Student students[], int *count) {
//和输入学生信息函数的逻辑一样,要保证学生的数量再合理范围内
if (*count >= MAX_STUDENTS) {
printf("学生信息已满(限定 %d 个),无法插入新学生。\n",MAX_STUDENTS);
return;
}
//提醒一下现在要插入第几个学生以及总共可以有多少个学生
printf("总共可以插入 %d 个学生,当前已有 %d 个学生,您将插入第 %d 个学生的信息哦!\n",MAX_STUDENTS, *count, *count + 1);
//以下内容逻辑和录入学生信息函数逻辑一模一样 ,就不重复解释了
printf("请输入新学生的姓名:");
scanf("%s", students[*count].name);
while (1) {
printf("请输入新学生的学号:");
if (scanf("%lld", &students[*count].id) != 1 || students[*count].id < 0 || students[*count].id > 9999999999) {
printf("学号无效(学号必须是最多10位的数字),请重新输入。\n");
while (getchar() != '\n'); // 清空输入缓冲区
} else if (checkIdExists(students, *count, students[*count].id)) {
printf("学号 %lld 已存在,您看看是不是输错了?请重新输入哦\n", students[*count].id);
} else {
break;
}
}
while (1) {
printf("请输入新学生的成绩:");
if (scanf("%f", &students[*count].score) != 1 || students[*count].score < 0 || students[*count].score > 750) {
printf("成绩无效(成绩必须在0到750之间),请重新输入哦。\n");
while (getchar() != '\n'); // 清空输入缓冲区
} else {
break;
}
}
(*count)++;
printf("\n新学生信息已插入!这是他/她的信息哦\n");
printf("姓名:%s 学号:%lld 成绩:%.2f\n", students[*count - 1].name, students[*count - 1].id, students[*count - 1].score);
}
//下面如果用冒泡排序,选择排序或插入排序比较浪费时间,如果有几十个学生还好
//要是成千上百个这种方法效率低下,所以我用快速排序
// 快速排序的辅助函数,用于交换两个学生的信息
void swap(struct Student *a, struct Student *b) {
struct Student temp = *a;
*a = *b;
*b = temp;
}
// 快速排序的辅助函数,用于分区
int partition(struct Student students[], int low, int high) {
//因为是按学生成绩score进行排名,所以用 students[].score
float pivot = students[high].score;
int i = (low - 1);
for (int j = low; j <= high - 1; j++) {
if (students[j].score > pivot) {
i++;
//调用swap函数快速将学生交换
swap(&students[i], &students[j]);
}
}
swap(&students[i + 1], &students[high]);
return (i + 1);
}
// 快速排序函数
void quickSort(struct Student students[], int low, int high) {
if (low < high) {
int pi = partition(students, low, high);
quickSort(students, low, pi - 1);
quickSort(students, pi + 1, high);
}
}
// 根据成绩排序并显示排名
void rankStudents(struct Student students[], int count) {
//如果当前系统没有学生的话
if (count == 0) {
printf("当前系统没有学生哦。\n");
return;
}
//调用quickSort函数对学生成绩进行降序排序
quickSort(students, 0, count - 1);
//打印排序后的学生信息
printf("这是同学们的考试成绩排名哦:\n");
//遍历打印学生按成绩排名的信息
for (int i = 0; i < count; i++) {
printf(" 第%d名: 姓名:%s 学号:%lld 成绩:%.2f\n", i + 1, students[i].name, students[i].id, students[i].score);
}
}
// 统计学生总数
void countStudents(int count) {
printf("当前一共有:%d个学生哦\n", count);
}
// 清屏函数
void clearScreen() {
// 判断操作系统并执行相应的命令
system("cls");
system("clear");
}
//清空缓冲区函数
void clearInputBuffer() {
// 清空缓冲区直到换行符或文件结束符
int c;
while ((c = getchar()) != '\n' && c != EOF) {}
}
// 退出系统确认函数
//因为我发现把这个功能写进主函数时程序比较卡,只能单独弄一个函数 ,然后再定义一个clearInputBuffer函数清除一下缓存
int confirmExit() {
//确认的方法和前面使用的差不多
int confirm;
for (int i = 0; i < 3; i++) {
int validInput = 0;
while (!validInput) {
//这里用了两个三元运算符
//三元运算符的执行顺序是从左到右的
//先判断第一个条件,如果条件为真,则返回第一个结果;如果条件为假,则继续评估下一个条件
printf("%s", (i == 0) ? "真的要退出系统吗亲 (1. 确定退出 / 0. 取消操作):"
: (i == 1) ? "要不要再检查一下亲 (1. 确定退出 / 0. 取消操作):"
: "确认信息无误就可以退出咯 (1. 确定退出 / 0. 取消操作):");
if (scanf("%d", &confirm) == 1 && (confirm == 0 || confirm == 1)) {
validInput = 1;
} else {
printf("无效的输入,您只能输入0或者1哦\n");
clearInputBuffer(); // 清空输入缓冲区
}
}
if (confirm == 0) {
printf("取消操作。\n");
return 0; // 返回0表示取消退出
}
}
return 1; // 返回1表示确认退出
}
// 保存学生信息到文件
void saveStudentsToFile(struct Student students[], int count) {
//写入文件信息
FILE *file = fopen(FILE_PATH, "w");
//出现异常时
if (file == NULL) {
perror("无法打开文件");
return;
}
//循环遍历输入信息到文件中
for (int i = 0; i < count; i++) {
fprintf(file, "姓名:%s 学号:%lld 成绩:%.2f\n", students[i].name, students[i].id, students[i].score);
}
//停止写入
fclose(file);
printf("学生信息已保存到文件 %s\n", FILE_PATH);
}
// 从文件加载学生信息
void loadStudentsFromFile(struct Student students[], int *count) {
//读取文件信息
FILE *file = fopen(FILE_PATH, "r");
//判断一下这玩意在不在
if (!file) {
printf("文件 %s 不存在,将创建新文件\n", FILE_PATH);
return;
}
// 初始化学生计数为0
*count = 0;
// 创建缓冲区line用于存储文件中的一行内容
char line[256];
// 逐行读取文件内容,直到文件结束或达到最大学生数
while (fgets(line, sizeof(line), file) && *count < MAX_STUDENTS) {
// 定位关键字段
// 在行中定位"姓名:","学号:","成绩:"这三个关键字段的位置
char *name_start = strstr(line, "姓名:");
char *id_start = strstr(line, "学号:");
char *score_start = strstr(line, "成绩:");
// 检查这一行是否所有关键字段都存在
if (!name_start || !id_start || !score_start){
// 只要缺一个就跳过这个的学生信息
continue;
}
// 提取姓名,姓名为字符串类型所以需要特殊处理
name_start += strlen("姓名:");
// 计算姓名长度:从"姓名:"开始到"学号:"结束之间
int name_len = id_start - name_start - 2; // 减去两个空格
// 检查姓名长度是否有效(大于0且小于结构体中姓名字段的最大长度)
if (name_len > 0 && (size_t)name_len < sizeof(students[*count].name)) {
// 复制姓名到学生结构体
strncpy(students[*count].name, name_start, name_len);
// 确保姓名字符串正确终止
students[*count].name[name_len] = '\0';
}
// 提取学号和成绩
sscanf(id_start + strlen("学号:"), "%lld", &students[*count].id);
sscanf(score_start + strlen("成绩:"), "%f", &students[*count].score);
// 成功加载一个学生,计数器加1
(*count)++;
}
fclose(file);
printf("已从文件 %s 加载 %d 条学生信息\n", FILE_PATH, *count);
}
int main() {
struct Student students[500];
int count = 0;
int choice;
int init = 0;
int clearscreen = 1;
// 加载已有的学生信息
loadStudentsFromFile(students, &count);
printf("成功加载 %d 名学生\n", count);
for (int i = 0; i < count; i++) {
printf("加载的学生:姓名:%s 学号:%lld 成绩:%.2f\n", students[i].name, students[i].id, students[i].score);
}
while(1){
// 调用清屏函数
if (init > 0) {
int validInput = 0;
while (!validInput) {
printf("输入 0 或 1 选择是否清屏:\n");
printf("1. 清屏\n");
printf("0. 不清屏\n");
printf("输入值为:");
//为了防止输入出0和1以外的其他数字影响程序
//所以要判断输入的是0还是1(在开始写这个代码时就遇到这个问题了)
// 即在输入的数字scanf("%d", &clearscreen) == 1 为整数合法的情况下
//的同时满足 clearscreen为0或者1的条件
if (scanf("%d", &clearscreen) == 1 && (clearscreen == 0 || clearscreen == 1)) {
//满足条件后将validInput的值改为1 ,!validInput就为0,退出while循环
validInput = 1;
if (clearscreen == 1) {
clearScreen(); // 调用清屏函数
}
} else {
//如果输入的不是0或1
printf("无效的输入,您只能输入0或者1哦\n");
while (getchar() != '\n'); // 清空输入缓冲区
}
}
}
init = 1;
printf("\n");
// 根据用户输入选择是否清屏
printf(" 学生信息管理系统\n");
printf(" 1. 输入学生信息\n");
printf(" 2. 查找学生信息\n");
printf(" 3. 删除学生信息\n");
printf(" 4. 修改学生信息\n");
printf(" 5. 插入学生信息\n");
printf(" 6. 学生成绩排名\n");
printf(" 7. 统计学生总数\n");
printf(" 8. 显示所有信息\n");
printf(" 0. 退出系统\n");
printf(" 请选择操作:只能输入0-8中的一个\n");
printf("\n输入值为:");
scanf("%d", &choice); // 输入操作选项
printf("\n");
switch (choice) {
case 1:
printf("输入学生信息\n");
inputStudent(students, &count);
printf("\n");
saveStudentsToFile(students, count);
break;
case 2:
printf("查找学生信息\n");
searchStudent(students, count);
printf("\n");
break;
case 3:
printf("删除学生信息\n");
deleteStudent(students, &count);
printf("\n");
saveStudentsToFile(students, count);
break;
case 4:
printf("修改学生信息\n");
modifyStudent(students, count);
printf("\n");
saveStudentsToFile(students, count);
break;
case 5:
printf("插入学生信息\n");
insertStudent(students, &count);
printf("\n");
saveStudentsToFile(students, count);
break;
case 6:
printf("根据成绩排名\n");
rankStudents(students, count);
printf("\n");
break;
case 7:
printf("统计学生总数\n");
countStudents(count);
printf("\n");
break;
case 8:
printf("显示所有信息\n");
printAllStudents(students, count); // 调用函数显示所有学生信息
printf("\n");
break;
case 0:
{
if (confirmExit()) {
// 调用确认函数
printf("\n");
printf("退出系统!!! 感谢您的使用 Ciallo~(∠.ω< )⌒★\n");
// 如果确认退出,则退出程序
exit(0);
}
saveStudentsToFile(students, count);
break;
}
default:
printf("无效的选项,您只能输入0~8中的一个哦。\n");
printf("\n");
}
}
return 0;
}


评论区