这个项目非常适合初学者到中级学习者,因为它涵盖了 C 语言的核心知识点,包括:

(图片来源网络,侵删)
- 结构体:封装不同类型的数据。
- 文件操作:将数据持久化存储到硬盘。
- 指针:高效地传递和操作数据。
- 函数:模块化编程,提高代码可读性和复用性。
- 循环和条件判断:控制程序流程。
- 动态内存分配:处理不确定数量的学生数据。
项目目标
创建一个命令行程序,可以实现对学生信息的增、删、改、查功能,并将数据保存在一个文件中,程序关闭后再次打开时,数据依然存在。
功能设计
- 添加学生信息:输入学生的学号、姓名和三门课程的成绩。
- 显示所有学生信息:以表格形式列出所有学生的数据。
- 根据学号查询学生:输入学号,查找并显示该学生的详细信息。
- 根据学号修改学生信息:输入学号,找到该学生并修改其姓名或成绩。
- 根据学号删除学生:输入学号,从系统中删除该学生记录。
- 退出系统:将所有数据保存到文件并退出程序。
第一步:定义数据结构
我们需要一个结构体来表示一个学生,一个学生应该包含学号、姓名和成绩。
// student.h (可以创建一个头文件,或者直接写在 main.c 中)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_NAME_LEN 50
#define COURSE_NUM 3
#define FILENAME "students.dat"
// 定义学生结构体
typedef struct {
int id; // 学号
char name[MAX_NAME_LEN]; // 姓名
float scores[COURSE_NUM]; // 三门课程的成绩
} Student;
第二步:主程序框架和函数声明
我们将所有功能都封装成独立的函数,这样 main 函数会非常清晰。
// main.c
#include "student.h"
// 函数声明
void showMenu();
void addStudent(Student *students, int *count);
void displayAllStudents(const Student *students, int count);
void searchStudent(const Student *students, int count);
void modifyStudent(Student *students, int count);
void deleteStudent(Student *students, int count);
void saveToFile(const Student *students, int count);
void loadFromFile(Student *students, int *count);
int main() {
Student students[100]; // 假设最多存储100个学生
int studentCount = 0;
int choice;
// 程序启动时,从文件加载数据
loadFromFile(students, &studentCount);
do {
showMenu();
printf("请输入您的选择: ");
scanf("%d", &choice);
switch (choice) {
case 1:
addStudent(students, &studentCount);
break;
case 2:
displayAllStudents(students, studentCount);
break;
case 3:
searchStudent(students, studentCount);
break;
case 4:
modifyStudent(students, studentCount);
break;
case 5:
deleteStudent(students, studentCount);
break;
case 6:
printf("正在保存数据并退出系统...\n");
saveToFile(students, studentCount);
break;
default:
printf("无效的输入,请重新选择!\n");
}
} while (choice != 6);
return 0;
}
// 显示主菜单
void showMenu() {
printf("\n========== 学生成绩管理系统 ==========\n");
printf(" 1. 添加学生信息\n");
printf(" 2. 显示所有学生信息\n");
printf(" 3. 查询学生信息\n");
printf(" 4. 修改学生信息\n");
printf(" 5. 删除学生信息\n");
printf(" 6. 退出系统\n");
printf("====================================\n");
}
第三步:实现核心功能函数
现在我们来逐一实现每个功能函数。

(图片来源网络,侵删)
添加学生
// 添加学生信息
void addStudent(Student *students, int *count) {
if (*count >= 100) {
printf("学生数量已达上限,无法添加!\n");
return;
}
Student s;
printf("请输入学号: ");
scanf("%d", &s.id);
// 检查学号是否已存在
for (int i = 0; i < *count; i++) {
if (students[i].id == s.id) {
printf("该学号已存在!\n");
return;
}
}
printf("请输入姓名: ");
scanf("%s", s.name);
printf("请输入三门课程的成绩 (用空格分隔): ");
for (int i = 0; i < COURSE_NUM; i++) {
scanf("%f", &s.scores[i]);
}
students[*count] = s;
(*count)++;
printf("学生信息添加成功!\n");
}
显示所有学生
// 显示所有学生信息
void displayAllStudents(const Student *students, int count) {
if (count == 0) {
printf("当前没有学生信息!\n");
return;
}
printf("\n%-10s %-20s %-10s %-10s %-10s\n", "学号", "姓名", "课程1", "课程2", "课程3");
printf("------------------------------------------------\n");
for (int i = 0; i < count; i++) {
printf("%-10d %-20s ", students[i].id, students[i].name);
for (int j = 0; j < COURSE_NUM; j++) {
printf("%-10.1f ", students[i].scores[j]);
}
printf("\n");
}
}
查询学生
// 查询学生信息
void searchStudent(const Student *students, int count) {
if (count == 0) {
printf("当前没有学生信息!\n");
return;
}
int id;
printf("请输入要查询的学号: ");
scanf("%d", &id);
for (int i = 0; i < count; i++) {
if (students[i].id == id) {
printf("\n%-10s %-20s %-10s %-10s %-10s\n", "学号", "姓名", "课程1", "课程2", "课程3");
printf("------------------------------------------------\n");
printf("%-10d %-20s ", students[i].id, students[i].name);
for (int j = 0; j < COURSE_NUM; j++) {
printf("%-10.1f ", students[i].scores[j]);
}
printf("\n");
return; // 找到后直接返回
}
}
printf("未找到学号为 %d 的学生!\n", id);
}
修改学生
// 修改学生信息
void modifyStudent(Student *students, int count) {
if (count == 0) {
printf("当前没有学生信息!\n");
return;
}
int id;
printf("请输入要修改的学号: ");
scanf("%d", &id);
for (int i = 0; i < count; i++) {
if (students[i].id == id) {
printf("找到学生: %s\n", students[i].name);
printf("请输入新的姓名 (直接回车保持原值): ");
char newName[MAX_NAME_LEN];
getchar(); // 清除输入缓冲区中的换行符
fgets(newName, MAX_NAME_LEN, stdin);
newName[strcspn(newName, "\n")] = 0; // 去掉fgets读取的换行符
if (strlen(newName) > 0) {
strcpy(students[i].name, newName);
}
printf("请输入新的三门课程成绩 (用空格分隔,-1表示保持原值): \n");
for (int j = 0; j < COURSE_NUM; j++) {
float newScore;
scanf("%f", &newScore);
if (newScore != -1) {
students[i].scores[j] = newScore;
}
}
printf("学生信息修改成功!\n");
return;
}
}
printf("未找到学号为 %d 的学生!\n", id);
}
删除学生
// 删除学生信息
void deleteStudent(Student *students, int *count) {
if (*count == 0) {
printf("当前没有学生信息!\n");
return;
}
int id;
printf("请输入要删除的学号: ");
scanf("%d", &id);
for (int i = 0; i < *count; i++) {
if (students[i].id == id) {
// 将后面的所有学生前移一位
for (int j = i; j < *count - 1; j++) {
students[j] = students[j + 1];
}
(*count)--; // 学生总数减一
printf("学号为 %d 的学生信息已删除!\n", id);
return;
}
}
printf("未找到学号为 %d 的学生!\n", id);
}
文件操作 (保存和加载)
这是实现数据持久化的关键。
// 将数据保存到文件
void saveToFile(const Student *students, int count) {
FILE *fp = fopen(FILENAME, "wb"); // "wb" 表示以二进制写入模式打开
if (fp == NULL) {
printf("无法打开文件进行保存!\n");
return;
}
// 先写入学生数量
fwrite(&count, sizeof(int), 1, fp);
// 再写入所有学生数据
fwrite(students, sizeof(Student), count, fp);
fclose(fp);
}
// 从文件加载数据
void loadFromFile(Student *students, int *count) {
FILE *fp = fopen(FILENAME, "rb"); // "rb" 表示以二进制读取模式打开
if (fp == NULL) {
// 文件不存在是正常情况,说明是第一次运行
printf("未找到数据文件,将创建新文件,\n");
*count = 0;
return;
}
// 先读取学生数量
fread(count, sizeof(int), 1, fp);
// 再读取所有学生数据
fread(students, sizeof(Student), *count, fp);
fclose(fp);
printf("成功从文件加载 %d 条学生记录,\n", *count);
}
第四步:编译和运行
- 保存代码:将所有代码保存为
main.c文件。 - 编译:打开终端或命令提示符,使用 GCC 进行编译。
gcc main.c -o student_manager
- 运行:
- 在 Windows 上:
student_manager.exe - 在 Linux/macOS 上:
./student_manager
- 在 Windows 上:
如何进一步改进(进阶方向)
这个项目已经是一个功能完整的 C 语言程序了,如果你想继续提升,可以考虑以下方向:
- 使用动态内存分配:用
malloc和realloc替换固定大小的数组Student students[100],这样系统可以处理任意数量的学生,直到内存耗尽。 - 更友好的用户界面:使用
ncurses库(在 Linux 上)或 Windows API(在 Windows 上)创建一个更美观的基于文本的用户界面。 - 增加排序功能:按总分、平均分或学号对学生进行排序后显示。
- 数据验证:在输入成绩时,确保输入的是有效数字(0-100分)。
- 错误处理:增加更健壮的错误处理机制,比如文件权限问题、磁盘空间不足等。
- 模块化:将每个功能(如
add.c,display.c,file_io.c)拆分到不同的.c文件中,并将对应的声明放到.h文件中,这是一个大型项目的标准做法。
这个项目是学习 C 语言的一个绝佳实践,希望你能动手试试!

(图片来源网络,侵删)
