目录
- 一、项目需求与模块设计
-
- [1.1 核心模块](#1.1 核心模块)
- [1.2 模块交互](#1.2 模块交互)
- [1.3 数据存储](#1.3 数据存储)
- 二、核心模块实现
-
- [2.1 教师管理模块](#2.1 教师管理模块)
- [2.2 课程管理模块](#2.2 课程管理模块)
- [2.3 模块关联](#2.3 模块关联)
- 三、项目整合与实战
-
- [3.1 整合三大模块](#3.1 整合三大模块)
- [3.2 权限控制](#3.2 权限控制)
- [3.3 测试](#3.3 测试)
一、项目需求与模块设计
1.1 核心模块
- 学生管理:复用第 41 - 43 篇的功能,能够实现学生信息的录入、查询、修改和删除等操作。学生信息包括学号、姓名、年龄、性别等基本信息。通过这些功能,可以方便地对学生数据进行维护和管理 ,比如添加新入学的学生信息,或者修改学生的成绩等。
- 教师管理:类似于学生管理模块,主要存储教师的工号、姓名以及所教授的课程信息。可以实现教师信息的录入,当学校新招聘教师时,将其信息录入系统;查看教师的详细信息,了解教师的授课情况;修改教师的信息,如教师更换所授课程时进行信息更新;删除教师信息,当教师离职时从系统中移除其相关数据。此外,还能统计教师任课数量,方便学校了解教师的教学工作量。
- 课程管理:用于存储课程号、课程名以及任课教师等信息。可以对课程信息进行有效的管理,确保课程数据的准确性和完整性。
1.2 模块交互
- 课程管理与教师的关联:一个教师可以教授多门课程,在系统中通过在课程管理模块中记录任课教师的工号,来建立课程与教师之间的联系。这样,当需要查询某门课程的任课教师时,或者查看某个教师所教授的所有课程时,都可以通过这种关联关系快速获取到相关信息。
- 学生管理与课程的关联:一个学生可以选择多门课程,在学生管理模块中添加选课功能,记录学生所选课程及成绩。通过这种方式,能够清晰地了解每个学生的选课情况和学习成绩,为学生的学业评估和教学管理提供数据支持。
1.3 数据存储
采用多个二进制文件分别存储学生、教师、课程数据。使用二进制文件存储数据具有以下优势:
- 存储效率高:二进制文件直接以二进制形式存储数据,不需要进行字符转换,占用空间小,能够更高效地利用存储空间。例如,对于整数类型的数据,在二进制文件中可以直接存储其对应的二进制值,而文本文件则需要将其转换为字符形式存储,会占用更多的空间。
- 读写速度快:二进制文件的读写操作相对简单,不需要进行复杂的解析和转换,因此读写速度比文本文件快很多。在处理大量数据时,这种速度优势尤为明显,能够提高系统的性能和响应速度。
- 数据完整性好:二进制文件能够准确地存储数据的原始形式,不会因为字符编码等问题导致数据丢失或损坏,保证了数据的完整性和准确性。
在具体实现中,分别创建 students.dat、teachers.dat、courses.dat 等二进制文件来存储学生、教师和课程数据。在写入数据时,使用 fwrite 函数将结构体数据写入文件;在读取数据时,使用 fread 函数从文件中读取数据到结构体变量中 。例如,对于学生数据的存储和读取:
c
#include <stdio.h>
#include <stdlib.h>
// 定义学生结构体
typedef struct {
int id;
char name[50];
int age;
char gender[10];
} Student;
// 将学生数据写入文件
void saveStudentsToFile(Student *students, int size, const char *filename) {
FILE *fp = fopen(filename, "wb");
if (fp == NULL) {
perror("无法打开文件");
return;
}
fwrite(students, sizeof(Student), size, fp);
fclose(fp);
}
// 从文件中读取学生数据
Student* loadStudentsFromFile(int *size, const char *filename) {
FILE *fp = fopen(filename, "rb");
if (fp == NULL) {
perror("无法打开文件");
return NULL;
}
fseek(fp, 0, SEEK_END);
long fileSize = ftell(fp);
fseek(fp, 0, SEEK_SET);
*size = fileSize / sizeof(Student);
Student *students = (Student*)malloc(*size * sizeof(Student));
fread(students, sizeof(Student), *size, fp);
fclose(fp);
return students;
}
通过这种方式,实现了学生、教师和课程数据的持久化存储,保证了系统在运行过程中数据的安全性和可靠性。
二、核心模块实现
2.1 教师管理模块
- 教师信息录入:定义教师结构体Teacher,包含工号id、姓名name和课程数组courses。在录入教师信息时,提示用户输入工号、姓名,然后根据实际情况输入教师所教授的课程信息,将这些信息存储到Teacher结构体变量中,并使用fwrite函数将其写入teachers.dat文件。示例代码如下:
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义教师结构体
typedef struct {
int id;
char name[50];
char courses[10][50];
int courseCount;
} Teacher;
// 教师信息录入函数
void addTeacher() {
FILE *fp = fopen("teachers.dat", "ab");
if (fp == NULL) {
perror("无法打开文件");
return;
}
Teacher teacher;
printf("请输入教师工号: ");
scanf("%d", &teacher.id);
printf("请输入教师姓名: ");
scanf("%s", teacher.name);
printf("请输入教师教授课程数量: ");
scanf("%d", &teacher.courseCount);
for (int i = 0; i < teacher.courseCount; i++) {
printf("请输入第 %d 门课程名称: ", i + 1);
scanf("%s", teacher.courses[i]);
}
fwrite(&teacher, sizeof(Teacher), 1, fp);
fclose(fp);
printf("教师信息录入成功!\n");
}
- 教师信息查看:从teachers.dat文件中读取教师信息并显示。使用fread函数从文件中读取数据到Teacher结构体数组中,然后遍历数组,输出每个教师的详细信息。示例代码如下:
c
// 教师信息查看函数
void viewTeachers() {
FILE *fp = fopen("teachers.dat", "rb");
if (fp == NULL) {
perror("无法打开文件");
return;
}
Teacher teacher;
while (fread(&teacher, sizeof(Teacher), 1, fp) == 1) {
printf("教师工号: %d\n", teacher.id);
printf("教师姓名: %s\n", teacher.name);
printf("教授课程数量: %d\n", teacher.courseCount);
for (int i = 0; i < teacher.courseCount; i++) {
printf("第 %d 门课程名称: %s\n", i + 1, teacher.courses[i]);
}
printf("\n");
}
fclose(fp);
}
- 教师信息修改:首先根据教师工号查找要修改的教师信息,然后提示用户输入需要修改的字段(姓名、课程等),更新结构体变量中的相应信息,最后将修改后的结构体写回文件。示例代码如下:
c
// 教师信息修改函数
void modifyTeacher() {
FILE *fp = fopen("teachers.dat", "rb+");
if (fp == NULL) {
perror("无法打开文件");
return;
}
int id, choice;
printf("请输入要修改的教师工号: ");
scanf("%d", &id);
Teacher teacher;
int found = 0;
while (fread(&teacher, sizeof(Teacher), 1, fp) == 1) {
if (teacher.id == id) {
found = 1;
printf("1. 修改姓名\n2. 修改课程\n请选择要修改的项: ");
scanf("%d", &choice);
switch (choice) {
case 1:
printf("请输入新的姓名: ");
scanf("%s", teacher.name);
break;
case 2:
printf("请输入新的课程数量: ");
scanf("%d", &teacher.courseCount);
for (int i = 0; i < teacher.courseCount; i++) {
printf("请输入第 %d 门课程名称: ", i + 1);
scanf("%s", teacher.courses[i]);
}
break;
default:
printf("无效选择!\n");
}
fseek(fp, -sizeof(Teacher), SEEK_CUR);
fwrite(&teacher, sizeof(Teacher), 1, fp);
printf("教师信息修改成功!\n");
break;
}
}
if (!found) {
printf("未找到该教师!\n");
}
fclose(fp);
}
- 教师信息删除:根据教师工号找到要删除的教师信息,将文件中该教师信息后面的内容向前移动覆盖要删除的信息,然后截断文件。示例代码如下:
c
// 教师信息删除函数
void deleteTeacher() {
FILE *fp = fopen("teachers.dat", "rb+");
if (fp == NULL) {
perror("无法打开文件");
return;
}
int id;
printf("请输入要删除的教师工号: ");
scanf("%d", &id);
Teacher teacher;
FILE *temp = fopen("temp.dat", "wb");
int found = 0;
while (fread(&teacher, sizeof(Teacher), 1, fp) == 1) {
if (teacher.id != id) {
fwrite(&teacher, sizeof(Teacher), 1, temp);
} else {
found = 1;
}
}
fclose(fp);
fclose(temp);
if (found) {
remove("teachers.dat");
rename("temp.dat", "teachers.dat");
printf("教师信息删除成功!\n");
} else {
remove("temp.dat");
printf("未找到该教师!\n");
}
}
- 统计教师任课数量:在读取教师信息时,统计每个教师结构体中的courseCount字段即可得到教师的任课数量。示例代码如下:
c
// 统计教师任课数量函数
void countTeacherCourses() {
FILE *fp = fopen("teachers.dat", "rb");
if (fp == NULL) {
perror("无法打开文件");
return;
}
Teacher teacher;
while (fread(&teacher, sizeof(Teacher), 1, fp) == 1) {
printf("教师 %s 任课数量: %d\n", teacher.name, teacher.courseCount);
}
fclose(fp);
}
2.2 课程管理模块
- 课程信息管理:定义课程结构体Course,包含课程号id、课程名name和任课教师工号teacherId。通过fwrite和fread函数实现课程信息的写入和读取文件操作。在录入课程信息时,提示用户输入课程号、课程名和任课教师工号;查看课程信息时,从文件中读取并显示;修改课程信息时,先查找再更新;删除课程信息时,类似教师信息删除的操作。示例代码如下:
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义课程结构体
typedef struct {
int id;
char name[50];
int teacherId;
} Course;
// 课程信息录入函数
void addCourse() {
FILE *fp = fopen("courses.dat", "ab");
if (fp == NULL) {
perror("无法打开文件");
return;
}
Course course;
printf("请输入课程号: ");
scanf("%d", &course.id);
printf("请输入课程名: ");
scanf("%s", course.name);
printf("请输入任课教师工号: ");
scanf("%d", &course.teacherId);
fwrite(&course, sizeof(Course), 1, fp);
fclose(fp);
printf("课程信息录入成功!\n");
}
// 课程信息查看函数
void viewCourses() {
FILE *fp = fopen("courses.dat", "rb");
if (fp == NULL) {
perror("无法打开文件");
return;
}
Course course;
while (fread(&course, sizeof(Course), 1, fp) == 1) {
printf("课程号: %d\n", course.id);
printf("课程名: %s\n", course.name);
printf("任课教师工号: %d\n", course.teacherId);
printf("\n");
}
fclose(fp);
}
// 课程信息修改函数
void modifyCourse() {
FILE *fp = fopen("courses.dat", "rb+");
if (fp == NULL) {
perror("无法打开文件");
return;
}
int id, choice;
printf("请输入要修改的课程号: ");
scanf("%d", &id);
Course course;
int found = 0;
while (fread(&course, sizeof(Course), 1, fp) == 1) {
if (course.id == id) {
found = 1;
printf("1. 修改课程名\n2. 修改任课教师工号\n请选择要修改的项: ");
scanf("%d", &choice);
switch (choice) {
case 1:
printf("请输入新的课程名: ");
scanf("%s", course.name);
break;
case 2:
printf("请输入新的任课教师工号: ");
scanf("%d", &course.teacherId);
break;
default:
printf("无效选择!\n");
}
fseek(fp, -sizeof(Course), SEEK_CUR);
fwrite(&course, sizeof(Course), 1, fp);
printf("课程信息修改成功!\n");
break;
}
}
if (!found) {
printf("未找到该课程!\n");
}
fclose(fp);
}
// 课程信息删除函数
void deleteCourse() {
FILE *fp = fopen("courses.dat", "rb+");
if (fp == NULL) {
perror("无法打开文件");
return;
}
int id;
printf("请输入要删除的课程号: ");
scanf("%d", &id);
Course course;
FILE *temp = fopen("temp.dat", "wb");
int found = 0;
while (fread(&course, sizeof(Course), 1, fp) == 1) {
if (course.id != id) {
fwrite(&course, sizeof(Course), 1, temp);
} else {
found = 1;
}
}
fclose(fp);
fclose(temp);
if (found) {
remove("courses.dat");
rename("temp.dat", "courses.dat");
printf("课程信息删除成功!\n");
} else {
remove("temp.dat");
printf("未找到该课程!\n");
}
}
- 按教师查询课程:遍历课程文件,当课程结构体中的teacherId与输入的教师工号相同时,输出该课程信息。示例代码如下:
c
// 按教师查询课程函数
void queryCoursesByTeacher() {
FILE *fp = fopen("courses.dat", "rb");
if (fp == NULL) {
perror("无法打开文件");
return;
}
int teacherId;
printf("请输入教师工号: ");
scanf("%d", &teacherId);
Course course;
int found = 0;
while (fread(&course, sizeof(Course), 1, fp) == 1) {
if (course.teacherId == teacherId) {
found = 1;
printf("课程号: %d\n", course.id);
printf("课程名: %s\n", course.name);
printf("任课教师工号: %d\n", course.teacherId);
printf("\n");
}
}
if (!found) {
printf("该教师没有授课信息!\n");
}
fclose(fp);
}
- 按课程查询教师:根据课程号在课程文件中找到对应的课程信息,获取任课教师工号,然后在教师文件中查找该工号对应的教师信息并输出。示例代码如下:
c
// 按课程查询教师函数
void queryTeacherByCourse() {
FILE *courseFp = fopen("courses.dat", "rb");
FILE *teacherFp = fopen("teachers.dat", "rb");
if (courseFp == NULL || teacherFp == NULL) {
perror("无法打开文件");
return;
}
int courseId;
printf("请输入课程号: ");
scanf("%d", &courseId);
Course course;
int found = 0;
while (fread(&course, sizeof(Course), 1, courseFp) == 1) {
if (course.id == courseId) {
found = 1;
Teacher teacher;
while (fread(&teacher, sizeof(Teacher), 1, teacherFp) == 1) {
if (teacher.id == course.teacherId) {
printf("教师工号: %d\n", teacher.id);
printf("教师姓名: %s\n", teacher.name);
printf("教授课程数量: %d\n", teacher.courseCount);
for (int i = 0; i < teacher.courseCount; i++) {
printf("第 %d 门课程名称: %s\n", i + 1, teacher.courses[i]);
}
printf("\n");
break;
}
}
break;
}
}
if (!found) {
printf("未找到该课程!\n");
}
fclose(courseFp);
fclose(teacherFp);
}
2.3 模块关联
在学生管理模块中添加 "选课" 功能,定义学生选课结构体StudentCourse,包含学生学号studentId、课程号courseId和成绩score。在学生选课时,提示用户输入学号、课程号和成绩,将这些信息存储到StudentCourse结构体变量中,并写入studentCourses.dat文件。示例代码如下:
c
#include <stdio.h>
#include <stdlib.h>
// 定义学生选课结构体
typedef struct {
int studentId;
int courseId;
float score;
} StudentCourse;
// 学生选课函数
void selectCourse() {
FILE *fp = fopen("studentCourses.dat", "ab");
if (fp == NULL) {
perror("无法打开文件");
return;
}
StudentCourse sc;
printf("请输入学生学号: ");
scanf("%d", &sc.studentId);
printf("请输入课程号: ");
scanf("%d", &sc.courseId);
printf("请输入成绩: ");
scanf("%f", &sc.score);
fwrite(&sc, sizeof(StudentCourse), 1, fp);
fclose(fp);
printf("选课成功!\n");
}
在需要查询学生所选课程及成绩时,从studentCourses.dat文件中读取数据,根据学号或课程号进行筛选和输出 。例如,查询某个学生所选课程及成绩的示例代码如下:
c
// 查询学生所选课程及成绩函数
void queryStudentCourses() {
FILE *fp = fopen("studentCourses.dat", "rb");
if (fp == NULL) {
perror("无法打开文件");
return;
}
int studentId;
printf("请输入学生学号: ");
scanf("%d", &studentId);
StudentCourse sc;
int found = 0;
while (fread(&sc, sizeof(StudentCourse), 1, fp) == 1) {
if (sc.studentId == studentId) {
found = 1;
printf("课程号: %d, 成绩: %.2f\n", sc.courseId, sc.score);
}
}
if (!found) {
printf("该学生没有选课记录!\n");
}
fclose(fp);
}
通过以上方式,实现了学生管理模块与课程管理模块之间的关联,能够准确记录学生所选课程及成绩,为校园信息管理系统提供了完整的数据支持。
三、项目整合与实战
3.1 整合三大模块
为了将学生管理、教师管理和课程管理这三大模块整合到一起,设计一个主菜单是必不可少的。主菜单为用户提供了一个清晰的操作界面,通过选择不同的选项,用户可以方便地切换到各个模块进行相应的操作。以下是主菜单的设计思路和示例代码:
c
#include <stdio.h>
#include <stdlib.h>
// 主菜单函数
void mainMenu() {
int choice;
while (1) {
system("cls"); // 清屏,对于Linux系统可使用system("clear")
printf("校园信息管理系统\n");
printf("1. 学生管理\n");
printf("2. 教师管理\n");
printf("3. 课程管理\n");
printf("4. 退出\n");
printf("请选择操作: ");
scanf("%d", &choice);
switch (choice) {
case 1:
// 调用学生管理模块的相关函数,例如显示学生管理子菜单
system("cls");
printf("学生管理模块\n");
// 这里可以添加具体的学生管理操作选项和调用相应函数
system("pause"); // 暂停,等待用户按键继续,对于Linux系统可使用getchar()两次实现类似效果
break;
case 2:
// 调用教师管理模块的相关函数,例如显示教师管理子菜单
system("cls");
printf("教师管理模块\n");
// 调用教师信息录入、查看等函数
viewTeachers();
system("pause");
break;
case 3:
// 调用课程管理模块的相关函数,例如显示课程管理子菜单
system("cls");
printf("课程管理模块\n");
// 调用课程信息录入、查看等函数
viewCourses();
system("pause");
break;
case 4:
exit(0); // 退出程序
default:
printf("无效选择,请重新输入!\n");
system("pause");
}
}
}
在上述代码中,mainMenu函数通过一个无限循环来持续显示主菜单。用户输入选择后,通过switch语句根据用户的选择调用相应模块的功能函数。如果用户选择退出(选项 4),则使用exit(0)函数退出程序。对于无效的选择,会提示用户重新输入。
3.2 权限控制
为了确保系统的安全性和数据的保密性,对不同用户设置不同的权限是非常重要的。在本系统中,简单地区分了管理员和普通用户。管理员拥有最高权限,可以对学生、教师和课程信息进行全面的操作,包括录入、修改、删除等;而普通用户仅具有查看学生和课程信息的权限,不能进行修改和删除等敏感操作。以下是权限控制的实现思路和示例代码:
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 模拟登录函数,返回1表示管理员,2表示普通用户,0表示登录失败
int login() {
char username[50], password[50];
printf("请输入用户名: ");
scanf("%s", username);
printf("请输入密码: ");
scanf("%s", password);
if (strcmp(username, "admin") == 0 && strcmp(password, "admin123") == 0) {
return 1; // 管理员
} else if (strcmp(username, "user") == 0 && strcmp(password, "user123") == 0) {
return 2; // 普通用户
} else {
return 0; // 登录失败
}
}
int main() {
int userType = login();
if (userType == 0) {
printf("登录失败,程序退出!\n");
return 1;
}
// 根据用户类型显示不同的菜单和操作权限
if (userType == 1) {
// 管理员菜单和操作
mainMenu();
} else {
// 普通用户菜单和操作
int choice;
while (1) {
system("cls");
printf("普通用户界面\n");
printf("1. 查看学生信息\n");
printf("2. 查看课程信息\n");
printf("3. 退出\n");
printf("请选择操作: ");
scanf("%d", &choice);
switch (choice) {
case 1:
// 调用查看学生信息函数
system("cls");
printf("学生信息查看\n");
// 这里添加查看学生信息的具体实现
system("pause");
break;
case 2:
// 调用查看课程信息函数
system("cls");
printf("课程信息查看\n");
// 这里添加查看课程信息的具体实现
system("pause");
break;
case 3:
exit(0);
default:
printf("无效选择,请重新输入!\n");
system("pause");
}
}
}
return 0;
}
在上述代码中,login函数用于模拟用户登录,根据输入的用户名和密码判断用户类型并返回相应的值。在main函数中,根据用户类型显示不同的菜单和提供不同的操作权限。如果是管理员用户,则调用mainMenu函数显示完整的系统功能菜单;如果是普通用户,则显示仅包含查看功能的菜单。
3.3 测试
为了验证系统的正确性和稳定性,需要对系统进行全面的测试。在测试过程中,模拟添加教师、创建课程、学生选课等操作,然后检查数据关联的正确性,确保各个模块之间的交互符合预期。以下是测试过程的示例:
- 添加教师:使用教师管理模块中的添加教师功能,添加一位教师,例如工号为 1001,姓名为 "张老师",教授课程为 "数学" 和 "物理"。
- 创建课程:使用课程管理模块中的添加课程功能,创建两门课程,课程号分别为 1 和 2,课程名分别为 "数学" 和 "物理",任课教师工号为 1001。
- 学生选课:假设已有学生学号为 2024001,使用学生管理模块中的选课功能,让该学生选择课程号为 1 和 2 的课程,并分别输入成绩,例如数学成绩为 90,物理成绩为 85。
- 验证数据关联 :
- 查询教师授课信息:通过按教师查询课程功能,输入教师工号 1001,检查是否能正确显示 "数学" 和 "物理" 两门课程信息。
- 查询课程任课教师:通过按课程查询教师功能,输入课程号 1,检查是否能正确显示任课教师 "张老师" 的信息。
- 查询学生选课及成绩:通过学生管理模块中的查询学生所选课程及成绩功能,输入学生学号 2024001,检查是否能正确显示所选课程 "数学" 和 "物理" 以及对应的成绩 90 和 85。
通过以上测试步骤,可以有效地验证系统中数据关联的正确性,确保校园信息管理系统能够正常运行,满足实际使用的需求。在实际测试过程中,还可以进一步增加测试用例,覆盖更多的边界情况和异常情况,以提高系统的可靠性和稳定性。