C语言项目实战:课程选课管理系统开发全记录
目录
- C语言项目实战:课程选课管理系统开发全记录
- 一、项目概述
- 1. 核心特性
- 2. 项目架构设计
- 1)文件结构
- 2)设计思想
- 二、 核心技术要点
- 1. 数据结构设计
- 1)基础设施
- 2)多类型支持
- 3)学习心得
- 2. 文件持久化与版本管理
- 1)读写
- 2)文件版本
- 3)学习心得
- 3. 安全的输入处理
- 1)输入验证
- 2)学习心得
- 4. 日志系统实现
- 1)日志接口
- 2)独立维护
- 3)学习心得
- 三、 开发过程中的学习收获
- 1. 函数模块化
- 2. 用户界面设计
- 3. 错误处理
- 4. 代码组织
- 5. 数据一致性
- 四、 项目功能详解
- 1.管理员功能
- 2.学生功能
- 3.默认账户
- 五、显示解决方案
- 1.字符编码
- 2.显示对齐
- 3.多颜色支持
- 五、总结
一、项目概述
在最近的学习和开发中,我完成了一个命令行课程选课管理系统的开发。这是一个完整的中型C语言项目,涵盖了数据结构、文件I/O、模块化设计等多个重要知识点。
项目仓库:CURED-FSZ/Courses_Selection_System_by_C
1. 核心特性
- 双角色系统:管理员和学生两套独立的操作逻辑
- 完整的CRUD操作:课程、学生、选课关系的增删改查
- 数据持久化:二进制文件存储,自动版本管理
- 日志系统:记录系统操作和用户行为
- 美化界面:彩色输出和用户友好的菜单
2. 项目架构设计
1)文件结构
Courses_Selection_System_by_C/ ├── main.c # 程序入口、主菜单、流程控制 ├── CMakeLists.txt # 构建配置 ├── README.md # 项目说明文档 │ ├── lib/ # 核心库模块 │ ├── based. c/h # 基础数据结构定义 │ ├── cmd. c/h # 控制台I/O和安全输入处理 │ ├── files.c/h # 文件读取/写入逻辑 │ ├── log.c/h # 日志系统 │ └── menus.c/h # 菜单显示和颜色处理 │ └── pages/ # 功能模块 ├── login.c/h # 用户登录逻辑 ├── admin.c/h # 管理员操作界面 └── student.c/h # 学生操作界面2)设计思想
采用了模块化设计和分层架构:
| 层级 | 说明 | 主要文件 |
|---|---|---|
| 表现层 | 用户界面和菜单 | menus.c, admin.c, student.c |
| 业务层 | 核心逻辑处理 | login.c, 各页面模块 |
| 数据层 | 数据结构和持久化 | based.c, files.c |
| 工具层 | 公共工具函数 | cmd.c, log.c |
这样的设计使得:
- 各模块职责清晰,易于维护
- 代码复用性高,减少重复
- 扩展性强,易于添加新功能
二、 核心技术要点
1. 数据结构设计
1)基础设施
使用结构体来组织数据:
设计了:
管理员, 学生, 课程, 选课关系, 数据联合体, 链表节点, 链表 等
typedefstructCourse{charname[COURSE_NAME];charid[COURSE_ID];charteacher[COURSE_TEACHER];charplace[COURSE_PLACE];intmax_num;}Course;// 具体的用户、课程、选课关系等数据结构// 存储在 lib/based.h 中2)多类型支持
为使得链表操作可以支持多类型,我使用联合体作为链表的val类型,并加上Type辅助标识,在保障类型安全的前提下让List得以存储多种数据
// 数据类型枚举typedefenumType{STU,COURSE,INTERRELATED}Type;// 通用数据联合体typedefunionData{Student student;Course course;InterRelated interRelated;}Data;// 链表节点结构体typedefstructNode{Data data;Type type;structNode*next;structNode*prev;}Node;// 链表结构体typedefstructList{Node*head;Node*tail;intsize;Type type;}List;3)学习心得
- 合理的结构体设计直接影响后续的所有操作效率
- 需要考虑字段的必要性和冗余性
- 要为数据版本升级预留足够的扩展空间
2. 文件持久化与版本管理
1)读写
系统使用二进制文件存储和读取数据:
......// 创建文件并写入初始数据configFile=fopen(FILE_NAME,"wb");// 写入版本信息fwrite(¤t_version,sizeof(int),1,configFile);// 学生数、课程数、关联数均为0constintzero=0;fwrite(&zero,sizeof(int),1,configFile);fwrite(&zero,sizeof(int),1,configFile);fwrite(&zero,sizeof(int),1,configFile);......2)文件版本
在开发过程中,我发现随着功能的更新,就的数据文件被读取之后会导致存入脏数据,所以引入了文件版本概念
CURRENT_VERSION // 当前数据格式版本 MIN_VERSION // 最小兼容版本 MAX_VERSION // 最大兼容版本应用内部存储了以上三个数据,而文件内会存储写入时的当前版本信息,在读取时会检查文件版本是否在min和max之间。如果文件不受支持,程序会新建一个文件存储。
3)学习心得
- 二进制文件比文本文件更高效,但可读性差
- 版本管理可以平滑地进行数据格式升级
- 需要在文件开头写入版本信息,以便向后兼容
3. 安全的输入处理
1)输入验证
在cmd.c模块中实现了安全的输入验证,这样可以避免一些因输入不合法导致的问题,同时也方便后期扩展
......intget_input_safety(char*target,constintmaxLength){if(!target||maxLength<=1){return1;}charbuffer[256];// 临时缓冲区if(!fgets(buffer,sizeof(buffer),stdin)){clearerr(stdin);return1;}// 检测并处理输入过长if(strchr(buffer,'\n')==NULL){intc;while((c=getchar())!='\n'&&c!=EOF){}set_color(YELLOW,DEFAULT);printf("输入过长,请控制在%d字符以内!\n",maxLength-1);reset_color();return1;}// 移除换行符buffer[strcspn(buffer,"\n")]=0;// 空输入检查if(strlen(buffer)==0){set_color(YELLOW,DEFAULT);puts("输入不能为空!");reset_color();return1;}if(strlen(buffer)>=maxLength){set_color(YELLOW,DEFAULT);printf("输入长度超出限制(最多%d字符)!\n",maxLength-1);reset_color();return1;}strncpy(target,buffer,maxLength-1);target[maxLength-1]='\0';return0;}......// 防止缓冲区溢出// 验证输入格式// 处理非法输入2)学习心得
- C语言的输入操作需要特别谨慎
- 使用
fgets()而不是gets()来防止缓冲区溢出 - 需要详细的输入验证和错误处理
4. 日志系统实现
1)日志接口
简易但实用的日志记录系统:
#defineINFO0#defineWARN1#defineERROR2/** * 日志记录函数 * @param level 日志级别 * @param message 日志信息 * @param print 是否打印到控制台(1:是,0:否) */voidlogger(intlevel,constchar*message,intprint);voidinit_log();voidclose_log();// 记录用户操作// 支持不同级别的日志(info, warning, error)// 便于调试和审计2)独立维护
日志系统的文件独立维护,以天为单位,既防止了日志囤积,也避免开太多文件
voidinit_log(){char*name=getTime(0);strcat(name,".log");logFile=fopen(name,"a");if(logFile!=NULL){free(name);}logger(INFO,"日志初始化完成",0);}voidclose_log(){if(logFile!=NULL){fclose(logFile);}}3)学习心得
- 日志系统对于大型项目的调试至关重要
- 应该记录关键操作,但不要过度日志
- 日志格式应该统一,便于分析
三、 开发过程中的学习收获
1. 函数模块化
- 将大功能分解为多个小函数
- 每个函数职责单一,易于测试
- 提高了代码的可读性和复用性
2. 用户界面设计
- 使用彩色输出提升用户体验
- 清晰的菜单导航结构
- 提供必要的操作反馈
3. 错误处理
- 需要考虑各种边界情况
- 提供有意义的错误提示
- 确保程序不会因为异常输入而崩溃
4. 代码组织
- 使用头文件声明接口
- 实现文件包含具体实现
- 避免循环依赖
5. 数据一致性
- 多个数据结构之间可能存在关联
- 删除操作需要考虑引用完整性
- 修改操作需要同步更新相关数据
四、 项目功能详解
1.管理员功能
| 功能 | 说明 |
|---|---|
| 课程管理 | 新增、删除、修改、查询课程信息 |
| 学生管理 | 新增、删除、修改、查询学生账户 |
| 选课维护 | 查看和维护学生的选课记录 |
| 密码修改 | 修改管理员登录密码 |
2.学生功能
| 功能 | 说明 |
|---|---|
| 选课 | 添加感兴趣的课程到个人课表 |
| 退课 | 从个人课表中移除课程 |
| 查看课程 | 浏览已选课程和课程信息 |
| 密码修改 | 修改学生登录密码 |
3.默认账户
管理员: 用户名:2300400001 密码:2300400001 建议:运行后立即修改密码以保证安全!五、显示解决方案
1.字符编码
我使用<windows.h>提供的接口让命令行窗口在UTF8编码运行,以获得良好的中文支持和一致性
// 设置控制台为UTF-8voidset_UTF8(void){SetConsoleCP(CP_UTF8);SetConsoleOutputCP(CP_UTF8);}2.显示对齐
由于中文和英文字符长度不统一,我自定义了对于函数进行UTF8下的字符对齐(部分特殊字符支持性尚不到位)和基于此的居中显示方法
// 计算UTF-8字符串在控制台中的显示宽度intget_display_width(constchar*str){if(!str)return0;intwidth=0;while(*str){constunsignedcharc=*str++;if(c<=0x7F)width+=1;// ASCIIelseif(c>=0xC0){width+=2;str+=2;}// UTF-8 汉字通常占3字节elsestr++;// 延续字节}returnwidth;}/** * 输出字符串,并填充空格使其宽度达到指定宽度 * @param str 要输出的字符串 * @param target_width 目标宽度 */voidprint_aligned(constchar*str,constinttarget_width){if(!str)return;constintcurrent_width=get_display_width(str);printf("%s",str);// 填充空格达到目标宽度for(inti=current_width;i<target_width;i++){putchar(' ');}}/** * @brief 居中打印字符串 * @param content 要居中的内容 * @param total_width 总宽度 * @param foreground 前景色 * @param background 背景色 */voidprint_centered(constchar*content,constinttotal_width,constintforeground,constintbackground){if(!content||total_width<=2){// 宽度太小无法容纳两侧 '|'printf("|%*s|\n",total_width-2,"");return;}constintcontent_width=get_display_width(content);intpadding_total=total_width-2-content_width;// 减去两侧 '|'if(padding_total<0)padding_total=0;constintpadding_left=padding_total/2;constintpadding_right=padding_total-padding_left;putchar('|');set_color(foreground,background);for(inti=0;i<padding_left;i++)putchar(' ');printf("%s",content);for(inti=0;i<padding_right;i++)putchar(' ');reset_color();putchar('|');putchar('\n');}3.多颜色支持
为支持丰富的控制台颜色,我实现了简单的基于官方API的颜色控制逻辑。
// 前景 / 背景颜色位#defineBLACK0x00#defineBLUE0x01#defineGREEN0x02#defineRED0x04#defineINTENSITY0x08// 亮色修饰位#defineCYAN(BLUE|GREEN)#defineMAGENTA(BLUE|RED)#defineYELLOW(RED|GREEN)#defineWHITE(RED|GREEN|BLUE)#defineDEFAULT0xFF// 内部辅助:将自定义常量映射到Windows前景色属性staticWORDMapForeground(constintcolor){if(color==DEFAULT)returnFOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE;WORD attr=0;if(color&BLUE)attr|=FOREGROUND_BLUE;if(color&GREEN)attr|=FOREGROUND_GREEN;if(color&RED)attr|=FOREGROUND_RED;if(color&INTENSITY)attr|=FOREGROUND_INTENSITY;returnattr;}// 内部辅助:将自定义常量映射到Windows背景色属性staticWORDMapBackground(constintcolor){if(color==DEFAULT)return0;WORD attr=0;if(color&BLUE)attr|=BACKGROUND_BLUE;if(color&GREEN)attr|=BACKGROUND_GREEN;if(color&RED)attr|=BACKGROUND_RED;if(color&INTENSITY)attr|=BACKGROUND_INTENSITY;returnattr;}// 设置前景色和背景色voidset_color(constintforeground,constintbackground){constWORD fgAttr=MapForeground(foreground);constWORD bgAttr=MapBackground(background);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),fgAttr|bgAttr);}// 重置为默认颜色voidreset_color(void){set_color(DEFAULT,DEFAULT);}五、总结
这个项目是一次很好的C语言综合实战,涉及:
- 数据结构设计
- 文件I/O管理
- 模块化设计
- 用户交互设计
- 版本和兼容性处理
通过这个项目,我深入理解了C语言在系统级编程中的应用,以及部分项目的架构设计思想。