课程选课管理系统
2026/5/28 5:52:39 网站建设 项目流程

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(&current_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语言在系统级编程中的应用,以及部分项目的架构设计思想。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询