C语言课程设计:学生选课系统
项目概述与需求分析
项目目标: 开发一个基于命令行的学生选课系统,模拟真实环境中学生选课的基本流程,该系统应能够实现学生信息管理、课程信息管理、选课、退课、查询等功能,为用户提供一个简洁、易用的操作界面。

(图片来源网络,侵删)
核心功能需求:
-
数据管理:
- 学生信息: 存储学生的学号、姓名、密码、已选课程列表。
- 课程信息: 存储课程的课程号、课程名、授课教师、学分、容量、已选人数。
-
核心功能:
- 学生登录: 学生通过学号和密码登录系统。
- 课程浏览: 登录后,学生可以查看所有可选课程列表。
- 选课: 学生可以选择一门未满且自己未选的课程。
- 退课: 学生可以退掉自己已经选中的课程。
- 查看个人课表: 学生可以查看自己当前已选的所有课程。
- 查询课程信息: 学生可以根据课程号或课程名查询特定课程的详细信息。
-
管理员功能 (可选,但推荐):
(图片来源网络,侵删)- 管理员登录: 使用预设的管理员账号和密码登录。
- 添加学生: 向系统中添加新的学生账号。
- 添加课程: 向系统中添加新的课程信息。
- 查看所有学生/课程信息: 查看系统中的所有学生或课程的详细信息。
-
数据持久化:
- 系统启动时,能从文件中加载学生和课程数据。
- 系统退出时,能将最新的学生和课程数据保存回文件,确保数据不丢失。
系统设计
1 数据结构设计
使用结构体来表示核心数据实体。
// 学生结构体
typedef struct {
char id[20]; // 学号
char name[50]; // 姓名
char password[20]; // 密码
int selected_courses[100]; // 存储已选课程的课程号
int course_count; // 已选课程数量
} Student;
// 课程结构体
typedef struct {
char id[20]; // 课程号
char name[50]; // 课程名
char teacher[30]; // 授课教师
int credit; // 学分
int capacity; // 课程容量
int selected_count; // 已选人数
} Course;
2 功能模块设计

(图片来源网络,侵删)
将整个系统划分为几个功能模块,每个模块由一组相关的函数实现。
- 主菜单模块:
showMainMenu()负责显示登录选项(学生/管理员/退出)。
- 学生模块:
studentLogin(): 学生登录验证。showStudentMenu(): 显示学生子菜单(选课、退课、查看课表等)。browseCourses(): 浏览所有课程。selectCourse(): 执行选课逻辑。dropCourse(): 执行退课逻辑。viewMySchedule(): 查看个人课表。
- 管理员模块:
adminLogin(): 管理员登录验证。showAdminMenu(): 显示管理员子菜单(添加学生、添加课程等)。addStudent(): 添加新学生。addCourse(): 添加新课程。showAllStudents(): 显示所有学生信息。showAllCourses(): 显示所有课程信息。
- 数据管理模块:
loadData(): 从文件students.dat和courses.dat加载数据。saveData(): 将数据保存到文件。initializeData(): 初始化一些默认的学生和课程数据(用于首次运行)。
- 工具模块:
findStudentById(): 根据学号查找学生。findCourseById(): 根据课程号查找课程。clearScreen(): 清屏函数,使界面更整洁。
3 文件结构设计
students.dat: 存储所有学生信息的二进制文件。courses.dat: 存储所有课程信息的二进制文件。
核心代码实现
下面是关键部分的C语言代码实现,你可以基于此框架进行扩展和完善。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h> // 用于 getch(),Windows下可用,Linux下需用 ncurses
#define MAX_STUDENTS 100
#define MAX_COURSES 50
#define FILENAME_STUDENTS "students.dat"
#define FILENAME_COURSES "courses.dat"
// --- 结构体定义 ---
typedef struct {
char id[20];
char name[50];
char password[20];
int selected_courses[100];
int course_count;
} Student;
typedef struct {
char id[20];
char name[50];
char teacher[30];
int credit;
int capacity;
int selected_count;
} Course;
// --- 全局变量 ---
Student students[MAX_STUDENTS];
Course courses[MAX_COURSES];
int student_count = 0;
int course_count = 0;
Student *current_student = NULL; // 当前登录的学生指针
// --- 函数声明 ---
void loadData();
void saveData();
void initializeData();
void clearScreen();
void showMainMenu();
void studentLogin();
void showStudentMenu();
void browseCourses();
void selectCourse();
void dropCourse();
void viewMySchedule();
void adminLogin();
void showAdminMenu();
void addStudent();
void addCourse();
void showAllStudents();
void showAllCourses();
Student* findStudentById(const char *id);
Course* findCourseById(const char *id);
// --- 主函数 ---
int main() {
loadData();
showMainMenu();
saveData(); // 退出前保存数据
return 0;
}
// --- 数据管理函数 ---
void loadData() {
FILE *sf = fopen(FILENAME_STUDENTS, "rb");
FILE *cf = fopen(FILENAME_COURSES, "rb");
if (sf) {
fread(&student_count, sizeof(int), 1, sf);
fread(students, sizeof(Student), student_count, sf);
fclose(sf);
}
if (cf) {
fread(&course_count, sizeof(int), 1, cf);
fread(courses, sizeof(Course), course_count, cf);
fclose(cf);
} else {
// 如果文件不存在,则初始化数据
initializeData();
}
}
void saveData() {
FILE *sf = fopen(FILENAME_STUDENTS, "wb");
FILE *cf = fopen(FILENAME_COURSES, "wb");
if (sf) {
fwrite(&student_count, sizeof(int), 1, sf);
fwrite(students, sizeof(Student), student_count, sf);
fclose(sf);
}
if (cf) {
fwrite(&course_count, sizeof(int), 1, cf);
fwrite(courses, sizeof(Course), course_count, cf);
fclose(cf);
}
}
void initializeData() {
// 初始化默认学生
strcpy(students[0].id, "2025001");
strcpy(students[0].name, "张三");
strcpy(students[0].password, "123456");
students[0].course_count = 0;
student_count++;
strcpy(students[1].id, "2025002");
strcpy(students[1].name, "李四");
strcpy(students[1].password, "123456");
students[1].course_count = 0;
student_count++;
// 初始化默认课程
strcpy(courses[0].id, "CS101");
strcpy(courses[0].name, "C语言程序设计");
strcpy(courses[0].teacher, "王老师");
courses[0].credit = 4;
courses[0].capacity = 100;
courses[0].selected_count = 0;
course_count++;
strcpy(courses[1].id, "CS102");
strcpy(courses[1].name, "数据结构");
strcpy(courses[1].teacher, "李老师");
courses[1].credit = 5;
courses[1].capacity = 80;
courses[1].selected_count = 0;
course_count++;
saveData(); // 保存初始数据到文件
}
// --- 界面与菜单函数 ---
void clearScreen() {
system("cls"); // Windows
// system("clear"); // Linux
}
void showMainMenu() {
int choice;
while (1) {
clearScreen();
printf("\n\n\n");
printf("\t\t\t学生选课系统\n");
printf("\t\t------------------------\n");
printf("\t\t 1. 学生登录\n");
printf("\t\t 2. 管理员登录\n");
printf("\t\t 0. 退出系统\n");
printf("\t\t------------------------\n");
printf("\t\t请选择: ");
scanf("%d", &choice);
getchar(); // 消耗换行符
switch (choice) {
case 1: studentLogin(); break;
case 2: adminLogin(); break;
case 0:
printf("\n\t感谢使用,再见!\n");
return;
default: printf("\t无效输入,请按任意键重试..."); getch();
}
}
}
// --- 学生模块 ---
void studentLogin() {
char id[20], password[20];
clearScreen();
printf("\n\n\n");
printf("\t\t\t学生登录\n");
printf("\t\t------------------------\n");
printf("\t\t学号: "); scanf("%s", id);
printf("\t\t密码: "); scanf("%s", password);
getchar();
current_student = findStudentById(id);
if (current_student != NULL && strcmp(current_student->password, password) == 0) {
printf("\t\t登录成功!按任意键进入菜单...");
getch();
showStudentMenu();
} else {
printf("\t\t学号或密码错误!按任意键返回...");
getch();
}
}
void showStudentMenu() {
int choice;
while (1) {
clearScreen();
printf("\n\n\n");
printf("\t\t欢迎, %s (学号: %s)\n", current_student->name, current_student->id);
printf("\t\t------------------------\n");
printf("\t\t 1. 浏览课程\n");
printf("\t\t 2. 选课\n");
printf("\t\t 3. 退课\n");
printf("\t\t 4. 查看我的课表\n");
printf("\t\t 0. 退出登录\n");
printf("\t\t------------------------\n");
printf("\t\t请选择: ");
scanf("%d", &choice);
getchar();
switch (choice) {
case 1: browseCourses(); break;
case 2: selectCourse(); break;
case 3: dropCourse(); break;
case 4: viewMySchedule(); break;
case 0:
current_student = NULL;
printf("\n\t已退出登录,按任意键返回主菜单...");
getch();
return;
default: printf("\t无效输入,请按任意键重试..."); getch();
}
}
}
void browseCourses() {
clearScreen();
printf("\n\n\n");
printf("\t\t\t所有课程列表\n");
printf("\t\t------------------------------------------------\n");
printf("\t\t%-10s %-20s %-10s %-5s %-5s\n", "课程号", "课程名", "教师", "学分", "状态");
printf("\t\t------------------------------------------------\n");
for (int i = 0; i < course_count; i++) {
char status[20];
if (courses[i].selected_count >= courses[i].capacity) {
strcpy(status, "已满");
} else {
strcpy(status, "可选");
}
printf("\t\t%-10s %-20s %-10s %-5d %-5s\n",
courses[i].id, courses[i].name, courses[i].teacher, courses[i].credit, status);
}
printf("\t\t------------------------------------------------\n");
printf("\t\t按任意键返回...");
getch();
}
void selectCourse() {
char course_id[20];
Course *course_to_select;
clearScreen();
printf("\n\n\n");
printf("\t\t\t选课\n");
printf("\t\t请输入要选的课程号: ");
scanf("%s", course_id);
getchar();
course_to_select = findCourseById(course_id);
if (course_to_select == NULL) {
printf("\t\t课程不存在!按任意键返回...");
getch();
return;
}
if (course_to_select->selected_count >= course_to_select->capacity) {
printf("\t\t课程已满!无法选择,按任意键返回...");
getch();
return;
}
// 检查是否已选
for (int i = 0; i < current_student->course_count; i++) {
if (current_student->selected_courses[i] == atoi(course_id)) { // 假设课程号是数字
printf("\t\t您已经选过这门课了!按任意键返回...");
getch();
return;
}
}
// 执行选课
current_student->selected_courses[current_student->course_count++] = atoi(course_id);
course_to_select->selected_count++;
printf("\t\t选课成功!按任意键返回...");
getch();
}
void dropCourse() {
char course_id[20];
int course_to_drop_idx = -1;
clearScreen();
printf("\n\n\n");
printf("\t\t\t退课\n");
printf("\t\t请输入要退的课程号: ");
scanf("%s", course_id);
getchar();
// 查找课程在学生列表中的索引
for (int i = 0; i < current_student->course_count; i++) {
if (current_student->selected_courses[i] == atoi(course_id)) {
course_to_drop_idx = i;
break;
}
}
if (course_to_drop_idx == -1) {
printf("\t\t您没有选这门课!按任意键返回...");
getch();
return;
}
// 执行退课
Course *course_to_drop = findCourseById(course_id);
if (course_to_drop) {
course_to_drop->selected_count--;
}
// 从学生列表中移除
for (int i = course_to_drop_idx; i < current_student->course_count - 1; i++) {
current_student->selected_courses[i] = current_student->selected_courses[i + 1];
}
current_student->course_count--;
printf("\t\t退课成功!按任意键返回...");
getch();
}
void viewMySchedule() {
clearScreen();
printf("\n\n\n");
printf("\t\t\t%s 的课表\n", current_student->name);
printf("\t\t------------------------------------------------\n");
printf("\t\t%-10s %-20s %-10s %-5s\n", "课程号", "课程名", "教师", "学分");
printf("\t\t------------------------------------------------\n");
int total_credits = 0;
for (int i = 0; i < current_student->course_count; i++) {
int c_id = current_student->selected_courses[i];
Course *c = findCourseById(c_id); // 需要修改 findCourseById 以支持整数ID
if (c) {
printf("\t\t%-10s %-20s %-10s %-5d\n", c->id, c->name, c->teacher, c->credit);
total_credits += c->credit;
}
}
printf("\t\t------------------------------------------------\n");
printf("\t\t总学分: %d\n", total_credits);
printf("\t\t按任意键返回...");
getch();
}
// --- 管理员模块 (示例) ---
void adminLogin() {
// 简化版,直接进入管理员菜单
printf("\n管理员登录成功!按任意键进入菜单...");
getch();
showAdminMenu();
}
void showAdminMenu() {
int choice;
while (1) {
clearScreen();
printf("\n\n\n");
printf("\t\t\t管理员菜单\n");
printf("\t\t------------------------\n");
printf("\t\t 1. 添加学生\n");
printf("\t\t 2. 添加课程\n");
printf("\t\t 3. 查看所有学生\n");
printf("\t\t 4. 查看所有课程\n");
printf("\t\t 0. 退出登录\n");
printf("\t\t------------------------\n");
printf("\t\t请选择: ");
scanf("%d", &choice);
getchar();
switch (choice) {
case 1: addStudent(); break;
case 2: addCourse(); break;
case 3: showAllStudents(); break;
case 4: showAllCourses(); break;
case 0:
printf("\n\t已退出登录,按任意键返回主菜单...");
getch();
return;
default: printf("\t无效输入,请按任意键重试..."); getch();
}
}
}
// --- 查找函数 ---
Student* findStudentById(const char *id) {
for (int i = 0; i < student_count; i++) {
if (strcmp(students[i].id, id) == 0) {
return &students[i];
}
}
return NULL;
}
Course* findCourseById(const char *id) {
for (int i = 0; i < course_count; i++) {
if (strcmp(courses[i].id, id) == 0) {
return &courses[i];
}
}
return NULL;
}
// --- 其他管理员函数实现 (略) ---
// addStudent(), addCourse(), showAllStudents(), showAllCourses() 的实现逻辑
// 与 browseCourses() 和 viewMySchedule() 类似,主要是数据写入和遍历输出。
// 注意:在添加学生/课程后,需要更新 student_count/course_count 并调用 saveData()。
如何运行与使用
-
编译代码:
- 将以上代码保存为
course_selection.c。 - 使用GCC编译器(Windows下推荐MinGW或TDM-GCC)进行编译:
gcc course_selection.c -o course_selection.exe
- 将以上代码保存为
-
运行程序:
- 在命令行中运行生成的可执行文件:
./course_selection.exe
- 在命令行中运行生成的可执行文件:
-
使用流程:
- 首次运行: 程序会自动创建
students.dat和courses.dat文件,并初始化默认数据(2个学生,2门课程)。 - 学生登录: 使用默认账号登录,
- 学号:
2025001 - 密码:
123456
- 学号:
- 选课/退课: 登录后,按照菜单提示操作,选课时注意查看课程状态是否为“可选”。
- 查看课表: 可以随时查看自己已选的课程和总学分。
- 管理员功能: 可以实现管理员菜单来添加更多学生和课程,丰富系统数据。
- 首次运行: 程序会自动创建
项目扩展与优化建议
这是一个基础版本,你可以从以下几个方面进行扩展和优化,使其功能更强大、代码更健壮:
-
数据结构优化:
- 使用动态内存分配(
malloc,realloc)代替固定大小的数组,以支持不确定数量的学生和课程。 - 使用链表来管理学生和课程,便于数据的插入和删除。
- 使用动态内存分配(
-
功能增强:
- 课程查询: 实现按课程名模糊查询。
- 成绩管理: 在学生结构体中增加成绩字段,实现成绩录入和查询功能。
- 时间冲突检测: 为课程增加“上课时间”字段,并在选课时检测是否与已选课程时间冲突。
- 密码加密: 使用简单的加密算法(如MD5, SHA1)对用户密码进行哈希存储,提高安全性。
- 用户角色区分: 增加教师角色,实现教师查看选课学生名单等功能。
-
代码健壮性:
- 输入验证: 对所有用户输入进行严格验证,防止非法输入导致程序崩溃(输入字母代替数字)。
- 文件操作错误处理: 对文件的打开、读写操作进行错误检查,处理文件不存在或读写失败的情况。
- 模块化与封装: 将功能进一步细化,每个函数只做一件小事,提高代码的可读性和可维护性。
-
用户体验:
- 分页显示: 当课程或学生数量很多时,实现分页显示功能。
- 排序功能: 允许用户按学分、课程名等对课程列表进行排序。
这个项目作为C语言的课程设计非常合适,它涵盖了结构体、文件操作、数组、函数、循环、条件判断等核心知识点,并有一定的综合性和实用性,祝你项目顺利!
