C语言课程设计:学生选课系统
项目概述
项目名称 学生选课管理系统

(图片来源网络,侵删)
项目目标 设计并实现一个基于命令行的学生选课系统,该系统应能够模拟学生选课的全过程,包括学生信息管理、课程信息管理、选课、退课、查询等功能,通过本项目,旨在加深对C语言核心概念(如结构体、数组、指针、文件I/O)的理解和应用,并锻炼基本的程序设计、模块化和调试能力。
开发环境
- 操作系统: Windows / Linux / macOS
- 编程语言: C语言 (C99标准)
- 编译器: GCC (MinGW on Windows), Clang, MSVC
- 开发工具: Visual Studio Code, Dev-C++, Sublime Text, Vim 等
功能设计
系统应至少包含以下核心功能:
- 主菜单: 提供所有功能的入口,用户可以通过数字选择操作。
- 学生信息管理:
- 添加学生: 手动输入学生信息(学号、姓名、密码),并保存。
- 显示所有学生: 列出系统中所有学生的基本信息。
- 根据学号查找学生: 快速定位并显示某个学生的详细信息。
- 课程信息管理:
- 添加课程: 手动输入课程信息(课程ID、课程名、授课教师、容量、已选人数)。
- 显示所有课程: 列出系统中所有课程的基本信息。
- 根据课程ID查找课程: 快速定位并显示某个课程的详细信息。
- 选课功能:
- 学生输入自己的学号和密码进行登录。
- 登录成功后,显示该学生已选课程和所有可选课程。
- 学生可以输入课程ID进行选课。
- 选课逻辑校验:
- 检查课程是否存在。
- 检查学生是否已经选过该课程(防止重复选课)。
- 检查课程是否已满(已选人数 >= 容量)。
- 所有校验通过后,更新学生选课列表和课程的已选人数。
- 退课功能:
- 学生登录后,可以查看自己已选的课程。
- 输入要退的课程ID。
- 退课逻辑校验:
检查学生是否已选该课程。
(图片来源网络,侵删) - 校验通过后,从学生选课列表中移除该课程,并减少课程的已选人数。
- 查询功能:
- 学生查询: 学生登录后,可以查看自己的个人信息和已选课程列表。
- 课程查询: (非登录状态)根据课程ID查询课程详情,包括选课学生名单。
- 数据持久化:
- 系统启动时,从文件(如
students.dat,courses.dat)中加载学生和课程数据。 - 每当学生或课程信息发生变更(增、删、改)时,立即将最新数据写回文件,确保数据不丢失。
- 系统启动时,从文件(如
数据结构设计
为了高效地管理数据,我们需要定义几个核心的结构体。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// --- 1. 学生结构体 ---
#define MAX_COURSES_PER_STUDENT 10 // 每个学生最多选多少门课
#define MAX_NAME_LEN 50
#define MAX_ID_LEN 20
typedef struct {
char id[MAX_ID_LEN]; // 学号
char name[MAX_NAME_LEN]; // 姓名
char password[MAX_ID_LEN]; // 密码
int selected_courses[MAX_COURSES_PER_STUDENT]; // 存储已选课程的ID数组
int course_count; // 已选课程数量
} Student;
// --- 2. 课程结构体 ---
#define MAX_STUDENTS_PER_COURSE 100 // 每门课最多多少人选
#define MAX_TEACHER_LEN 50
typedef struct {
char id[MAX_ID_LEN]; // 课程ID
char name[MAX_NAME_LEN]; // 课程名
char teacher[MAX_TEACHER_LEN]; // 授课教师
int capacity; // 课程容量
int enrolled_count; // 已选人数
int enrolled_students[MAX_STUDENTS_PER_COURSE]; // 存储已选学生的学号数组
} Course;
// --- 3. 全局数据管理 ---
// 使用数组来存储学生和课程,简单直观,适合课程设计规模
Student students[1000]; // 假设最多1000名学生
Course courses[100]; // 假设最多100门课程
int student_count = 0;
int course_count = 0;
模块划分与函数设计
将系统功能分解为独立的函数,便于管理和维护。
| 模块 | 函数名 | 功能描述 |
|---|---|---|
| 主菜单 | main_menu() |
显示主菜单,根据用户输入调用相应功能函数 |
| 文件操作 | load_data() |
启动时从文件加载数据到内存 |
save_data() |
退出或数据变更时,将内存数据保存到文件 | |
| 学生管理 | add_student() |
添加新学生 |
display_all_students() |
显示所有学生信息 | |
find_student() |
按学号查找学生 | |
| 课程管理 | add_course() |
添加新课程 |
display_all_courses() |
显示所有课程信息 | |
find_course() |
按课程ID查找课程 | |
| 选课/退课 | student_login() |
学生登录验证 |
select_course() |
学生选课核心逻辑 | |
drop_course() |
学生退课核心逻辑 | |
| 查询功能 | query_student_info() |
学生查询个人信息和已选课 |
query_course_detail() |
查询课程详细信息(含学生名单) |
核心代码示例
这里展示几个关键函数的实现思路。
main() 函数 - 程序入口

(图片来源网络,侵删)
int main() {
load_data(); // 启动时加载数据
int choice;
while (1) {
main_menu();
printf("请输入您的选择: ");
scanf("%d", &choice);
switch (choice) {
case 1: // 学生管理
// ... 调用学生管理相关函数 ...
break;
case 2: // 课程管理
// ... 调用课程管理相关函数 ...
break;
case 3: // 学生登录选课
student_login();
break;
case 4: // 退出系统
save_data(); // 退出前保存数据
printf("感谢使用,再见!\n");
exit(0);
default:
printf("无效的输入,请重新选择!\n");
}
}
return 0;
}
load_data() 和 save_data() 函数 - 文件I/O
void save_data() {
// 保存学生数据
FILE *student_file = fopen("students.dat", "wb");
if (student_file) {
fwrite(&student_count, sizeof(int), 1, student_file);
fwrite(students, sizeof(Student), student_count, student_file);
fclose(student_file);
}
// 保存课程数据
FILE *course_file = fopen("courses.dat", "wb");
if (course_file) {
fwrite(&course_count, sizeof(int), 1, course_file);
fwrite(courses, sizeof(Course), course_count, course_file);
fclose(course_file);
}
}
void load_data() {
// 加载学生数据
FILE *student_file = fopen("students.dat", "rb");
if (student_file) {
fread(&student_count, sizeof(int), 1, student_file);
fread(students, sizeof(Student), student_count, student_file);
fclose(student_file);
}
// 加载课程数据
FILE *course_file = fopen("courses.dat", "rb");
if (course_file) {
fread(&course_count, sizeof(int), 1, course_file);
fread(courses, sizeof(Course), course_count, course_file);
fclose(course_file);
}
}
select_course() 函数 - 核心业务逻辑
void select_course() {
char student_id[MAX_ID_LEN], password[MAX_ID_LEN];
printf("请输入学号: ");
scanf("%s", student_id);
printf("请输入密码: ");
scanf("%s", password);
// 1. 查找并验证学生
int student_idx = -1;
for (int i = 0; i < student_count; i++) {
if (strcmp(students[i].id, student_id) == 0 && strcmp(students[i].password, password) == 0) {
student_idx = i;
break;
}
}
if (student_idx == -1) {
printf("学号或密码错误!\n");
return;
}
Student *s = &students[student_idx];
// 2. 显示可选课程
printf("\n--- 可选课程列表 ---\n");
display_all_courses();
// 3. 输入要选的课程ID
char course_id[MAX_ID_LEN];
printf("\n请输入要选的课程ID: ");
scanf("%s", course_id);
// 4. 查找课程
int course_idx = -1;
for (int i = 0; i < course_count; i++) {
if (strcmp(courses[i].id, course_id) == 0) {
course_idx = i;
break;
}
}
if (course_idx == -1) {
printf("课程不存在!\n");
return;
}
Course *c = &courses[course_idx];
// 5. 校验选课条件
if (c->enrolled_count >= c->capacity) {
printf("选课失败:课程 %s 已满!\n", c->name);
return;
}
for (int i = 0; i < s->course_count; i++) {
if (s->selected_courses[i] == course_idx) { // 注意:这里存的是课程在数组中的索引
printf("选课失败:您已经选过这门课了!\n");
return;
}
}
// 6. 执行选课操作
s->selected_courses[s->course_count] = course_idx;
s->course_count++;
c->enrolled_students[c->enrolled_count] = student_idx; // 存储学生在数组中的索引
c->enrolled_count++;
printf("选课成功!您已成功选入课程 %s,\n", c->name);
}
系统扩展建议(进阶)
如果觉得基础功能已经完成,可以尝试以下扩展来提升项目质量:
-
使用链表代替数组:
- 优点: 动态分配内存,可以处理任意数量的学生和课程,避免数组大小限制。
- 实现: 定义
StudentNode和CourseNode结构体,包含指向下一个节点的指针。
-
优化数据结构:
- 使用哈希表: 对于按ID查找频繁的操作(如登录、查课程),使用哈希表可以将查找时间复杂度从 O(n) 降到接近 O(1),极大提升性能。
-
增加用户角色:
- 管理员角色: 拥有所有权限,包括删除学生/课程、修改信息等。
- 教师角色: 可以查看自己教授的课程和选课学生名单。
- 在登录时增加角色选择。
-
图形用户界面:
- 使用
EasyX(Windows) 或GTK/Qt(跨平台) 等库,将命令行界面改为图形界面,提升用户体验。
- 使用
-
更完善的错误处理:
- 对文件打开失败、内存分配失败等情况进行更健壮的处理,而不是简单地
exit。
- 对文件打开失败、内存分配失败等情况进行更健壮的处理,而不是简单地
-
增加排序和统计功能:
- 按选课人数对课程进行排序。
- 统计每门课的选课率、热门课程等。
这个学生选课系统设计方案涵盖了从需求分析到代码实现的全过程,通过模块化的设计,将复杂问题分解为小而美的函数,使得代码结构清晰、易于维护,文件I/O的设计保证了数据的持久化,核心的选课/退课逻辑则锻炼了对业务流程和边界条件的处理能力。
你可以按照这个框架,逐步实现每个函数,最终完成一个功能完整、结构良好的C语言课程设计项目,祝你项目顺利!
