从建库到交报告:C++ Builder+Access选课系统课程设计流水线

发布时间:2026/11/3 3:16:24
从建库到交报告:C++ Builder+Access选课系统课程设计流水线 简介《软件工程课程设计指导书》是面向计算机科学与技术专业学生与软件工程初学者的课程设计实践文档由周兵编写用于支撑 10 学时的上机实践帮助读者按照软件工程思想与面向对象方法完成一个小型软件系统。内容以选课系统为完整案例串联需求分析、软件设计、软件测试与源代码编写涉及 C Builder 6.0 与 Access 2000 环境说明了用例图、序列图、对象模型、状态图等文档的撰写要求附录还包含 ODBC 数据源建立、C Builder 上手与数据库连接的操作指引。压缩包内仅 1 个 doc 文件约 539KB体积轻便便于打印或随课程进度查阅。目前已有 99 人学习。读者可据此明确设计题目、字段与功能清单、报告结构与成绩考核方式减少选题偏差和文档返工也适合教师作为课程设计任务书模板参考。1. 一份 10 学时的软件工程课程设计指导书其实是一条从建库到交报告的流水线第一次翻这份《软件工程课程设计指导书》我以为又是那种满篇「需求分析的重要性」的理论册子。结果第 3 页就开始教人在 BDE Administrator 里右键建数据源第 5 页已经在拖 ADOConnection 控件最后几页直接甩出报告目录用例图、序列图、对象模型、状态图、至少 20 组测试数据、只交手写源码。这份指导书真正的价值不是讲软件工程概念而是把「需求分析 → 对象建模 → 建库连库 → 功能实现 → 测试设计 → 交报告」拧成一条 10 学时能跑完的流水线落点是所有人都熟的选课系统。技术栈是 C Builder 6.0 加 Access 2000老得掉牙却老得刚好没有框架帮你藏东西每个环节都看得见摸得着。适合两类人一类是要交课程设计的学生照着走能少返工一类是想补一遍完整开发流程的开发者把它当成最小可复现的样板工程。2. 技术栈与工程结构C Builder 6.0 Access 2000 这套组合怎么摆平2.1 为什么是这套「老古董」而不是更现代的技术栈指导书把实验环境钉死在单机 Windows、C Builder 6.0、Access 2000很多人第一反应是过时。放到 10 学时、单人单机的约束下看这个选择其实相当合理理由有三条值得说清楚因为后面所有工程组织方式都从这个前提推导出来。第一Access 的 .mdb 是文件型数据库不需要装服务、不需要配账号权限、不需要建实例把文件拷进工程目录就能跑10 学时里省掉的部署时间足够多写两个功能页面。第二C Builder 的 VCL 组件面板里有现成的 ADO 页签拖一个 TADOConnection、一个 TADOQuery、一个 TDBGrid界面和数据就接上了学生不用先花两天学 Web 框架的路由和模板。第三先修课程写的是「一门计算机高级语言、C 语言、数据库系统概论」选 C Builder 能让 C 语法和数据库 SQL 同时用上不会出现「学了三个学期课程设计里一个都用不到」的尴尬。代价也很明确这套工具链只能跑 32 位字符串默认按 ANSI 处理BDE 和 ODBC 两层配置叠在一起任何一层配错都表现为「连不上」。我在别的项目里带新人时也遇到过类似情况老工具链的坑不在代码在环境。对比项C Builder 6.0 Access 2000现在常见的替代方案部署成本拷贝 .mdb 配一个 ODBC 数据源起服务、建库、配账号代码可见性全部手写在 Unit 里无框架黑箱框架分层多初学者容易只改配置学习曲线会 C 就能上手拖控件需要先懂框架生命周期中文支持ANSI 代码页需显式处理默认 UTF-8适合场景单人单机、10 学时、要交完整文档团队协作、需要联网部署如果你想原汁原味复现这个课程设计就按老栈走如果你只是想借它的流程练手把 C Builder 换成任何带可视化界面的语言都行但数据库建模、事务处理、测试用例设计这三块不能省那才是指导书真正想训练的部分。2.2 指导书对应的工程目录与文件清单指导书正文没给目录但从它的操作步骤能反推出一个最小工程该长什么样。C Builder 6.0 的项目文件是 .bpr窗体单元是 .cpp .h .dfm 三件套数据库文件独立放在数据目录里ODBC 数据源指向绝对路径。我一般会按下面这个结构建工程一是让 .mdb 跟源码分开放二是编译产物不污染提交目录。XuanKe/ ├── XuanKe.bpr # 工程文件记录所有 Unit 和编译选项 ├── XuanKe.cpp # 程序入口Application-Run() ├── UnitMain.cpp / .h / .dfm # 主窗体登录 功能导航 ├── UnitSelect.cpp / .h / .dfm # 选课界面 ├── UnitScore.cpp / .h / .dfm # 成绩查询界面 ├── UnitData.cpp / .h # 数据模块全局 ADOConnection 封装 ├── data/ │ └── xk.mdb # Access 2000 数据库文件 ├── doc/ # 报告、用例图、序列图、状态图 └── test/ └── cases.csv # 20 组测试数据的落盘记录DFM 文件是窗体布局的二进制文本描述控件位置、属性、事件绑定都记在里面所以改名、挪控件最好在 IDE 里做手改 DFM 很容易让工程打不开。UnitData 单独拆出来是我加的习惯指导书里是直接在 Form1 上放 ADOConnection功能一多每个窗体各连一次数据库连接数上去以后锁表现象会非常难查。2.3 环境落地把 2000 年代的工具链在今天跑起来真正的第一道坎不是写代码是让连接测试通过。指导书附录里的步骤是对的但它默认你在一台全新的、32 位的、装好 BDE 的机器上操作。今天的机器大多 64 位默认的 ODBC 管理器也是 64 位版本打开以后根本找不到 Microsoft Access Driver (*.mdb)很多人卡在这一步就放弃了。:: 32 位 ODBC 数据源管理器老工具链必须从这里进 %SystemRoot%\SysWOW64\odbcad32.exe :: 64 位版本只能看到 64 位驱动对 C Builder 6 编译出的程序无效 %SystemRoot%\System32\odbcad32.exe :: BDE 管理器建别名、Append To SQL Links 都在这里 C:\Program Files (x86)\Borland\BDE\BDEADMIN.EXE两个路径的区别是 32 位程序和 64 位驱动的隔离机制造成的C Builder 6.0 编译出来的 exe 是 32 位进程只能加载 32 位的 ODBC 驱动所以数据源必须在 SysWOW64 那个入口里建。建完数据源以后回到 BDE Administrator右键 Databases 新建一个别名Driver Name 选 Microsoft Access Driver (*.mdb)然后在 Definition 页把 ODBC DSN 填成刚才建的名称右键 Apply字体变黑说明打通了。还有一个细节别把工程放在带空格的路径下。BDE 早期版本解析别名路径时对空格处理不稳路径里带个空格就变成「数据源存在但打不开」排查起来非常费时间。装一个干净的虚拟机把工具链、Access、BDE 全装进去做完快照后面所有实验都在快照上跑比在真机上反复折腾快得多。3. 数据模型拆解一张表撑不起选课系统字段怎么归到 5 个实体3.1 从字段清单到实体关系指导书列的 26 个字段要拆开看指导书要求「所涉及的字段至少应包括」并给了一长串从学生姓名一直到总评成绩。新手最常见的做法是建一张大表把这些字段全塞进去然后发现一条选课记录里重复存了十几次学生信息和教师信息改一个电话号码要更新几十行。这一步其实就是软件工程里讲的实体识别只是指导书没明说怎么分。我的拆法是以「谁在描述谁」为标准描述人的归人描述课的归课描述「这门课在这个学期由谁开」的归教学班描述「谁选了什么课、考了多少分」的归选课记录。四个实体加一个关系表一共五张表正好覆盖全部字段。原始字段归入表处理说明学生姓名、学号、登陆密码、性别、出生年月、籍贯、学生电话、地址、家庭地址学生「地址」与「家庭地址」是两个概念前者按联系地址处理拆成两个字段教师号、教师姓名、教研室、职称、性别、教师电话教师性别字段在两张表里同名建表时要避免混用课名、课号、学分、先行课号、课时课程先行课号是自引用外键指向课程表自己课号、教师号、开课教室、人数限制、选课人数开课同一门课可以开多个班所以主键是开课号而非课号学号、课号、考试成绩、平时成绩、总评成绩选课主键是学号 课号的联合主键天然挡住重复选课这里有两个容易被忽略的点。先行课号是课程表对自身的外键用图形化建模时那根箭头会绕回同一个矩形画不出来就说明关系没想清楚。总评成绩不必单独存储它由考试成绩和平时成绩按比例算出存进去就有出现不一致的风险常见做法是查询时现算或者用一个计算字段。3.2 建表 SQL 与主外键把文档里的对象模型落到 AccessAccess 2000 默认是图形化建表但指导书要求报告里交对象模型模型和实际表结构必须对得上。我习惯先写 DDL 再用 ADO 执行这样表结构可复现换个环境重新建库不用再点一遍鼠标。-- Jet 4.0 方言文本类型必须带长度主键在字段级或表级都可以声明 CREATE TABLE 学生 ( 学号 TEXT(10) NOT NULL, 姓名 TEXT(20) NOT NULL, 密码 TEXT(20) NOT NULL, 性别 TEXT(2), 出生年月 DATETIME, 籍贯 TEXT(30), 联系地址 TEXT(50), 家庭住址 TEXT(50), 学生电话 TEXT(20), CONSTRAINT PK_学生 PRIMARY KEY (学号) ); CREATE TABLE 课程 ( 课号 TEXT(8) NOT NULL, 课名 TEXT(40) NOT NULL, 学分 INTEGER, 课时 INTEGER, 先行课号 TEXT(8), -- 自引用外键可为空 CONSTRAINT PK_课程 PRIMARY KEY (课号), CONSTRAINT FK_课程_先行 FOREIGN KEY (先行课号) REFERENCES 课程(课号) ); CREATE TABLE 选课 ( 学号 TEXT(10) NOT NULL, 课号 TEXT(8) NOT NULL, -- 这里存的是开课号 考试成绩 INTEGER, 平时成绩 INTEGER, CONSTRAINT PK_选课 PRIMARY KEY (学号, 课号), CONSTRAINT FK_选课_学生 FOREIGN KEY (学号) REFERENCES 学生(学号), CONSTRAINT FK_选课_开课 FOREIGN KEY (课号) REFERENCES 开课(开课号) );主键选联合主键是关键一步学号加开课号的组合唯一重复选课会在数据库层被直接拒绝不用在代码里先查一遍再插。外键声明在 Jet 里生效删学生记录时如果有选课记录会被拦住这个约束在调试阶段很烦但它能防止程序 bug 把成绩数据变成孤儿行。索引方面选课表按学号、开课表按课号各建一个普通索引查询界面翻页时会明显快一截。3.3 选课人数与人数限制一个必须用事务兜住的字段指导书的功能列表里选课和退课各占一条但没提人数限制怎么处理。这是整个系统里唯一有并发语义的地方也是报告里最容易写不出东西的地方。教学班有人数限制选课人数是实时统计值如果代码写成「先查还有没有名额再插一条记录再更新计数」两个操作之间有窗口人数超限几乎是必然的虽然课程设计是单机但教师端批量导入和学生在另一台机器上同时操作的情况助教检查时一测就露。正确的做法是把「占名额」做成一条带条件的原子更新条件不成立就说明满了靠返回值判断而不是先查后改。// UnitSelect.cpp —— 选课占名额与写记录必须在同一个事务里 bool TFormSelect::DoSelect(const AnsiString sid, const AnsiString openId) { TADOQuery *q new TADOQuery(NULL); q-Connection DM-Conn; // 全局数据模块里的连接 try { DM-Conn-BeginTrans(); // 开启事务 // 第一步只有还有余量时才加一条件写在 WHERE 里保证原子性 q-SQL-Text UPDATE 开课 SET 选课人数 选课人数 1 WHERE 开课号 :oid AND 选课人数 人数限制; q-Parameters-ParamByName(oid)-Value openId; q-ExecSQL(); if (q-RowsAffected ! 1) { // 0 行受影响 满员或课号不存在 DM-Conn-RollbackTrans(); return false; } // 第二步写入选课记录重复选课会被联合主键拒绝 q-SQL-Text INSERT INTO 选课(学号, 课号) VALUES(:sid, :oid); q-Parameters-ParamByName(sid)-Value sid; q-Parameters-ParamByName(oid)-Value openId; q-ExecSQL(); DM-Conn-CommitTrans(); // 两步都成功才提交 return true; } catch (Exception e) { DM-Conn-RollbackTrans(); // 任何异常都回滚计数不会虚增 ShowMessage(选课失败 e.Message); return false; } }三个参数要盯住Conn 是全局唯一连接事务是在连接级别生效的所以同一窗体里千万别另开第二个 TADOConnectionRowsAffected 取的是受影响行数这条 UPDATE 自然只可能影响 0 或 1 行判等条件很干净开课号存进选课表的课号字段是因为同一门课分多个班退课和成绩都必须按班区分。退课是镜像操作先删记录再减一顺序反了会出现「计数减了但记录还在」的脏数据。4. 核心功能实现登录、选课、退课、成绩查询的代码骨架4.1 数据模块TADOConnection 的连接串怎么写才不飘指导书附录第三步讲的是拖一个 ADOConnection 然后点 Build 按钮向导生成连接串这条路能通但生成出来的字符串是一长串 Provider 参数换台机器就失效。我在 UnitData 里做了一层封装把连接串写死成引用 ODBC 数据源名的形式只要机器上建了同名 DSN工程拷到哪里都能用。// UnitData.cpp —— 全局数据模块所有窗体共用一条连接 TADOConnection* TDM::Conn NULL; TADOConnection* TDM::GetConn() { if (Conn NULL) { Conn new TADOConnection(NULL); Conn-LoginPrompt false; // 关掉每次弹出的登录框 Conn-ConnectionTimeout 5; // 连不上时 5 秒超时不干等 Conn-ConnectionString ProviderMSDASQL.1;Persist Security InfoFalse; Data Source选课数据源; // 与 ODBC 管理器里的名字逐字一致 try { Conn-Connected true; } catch (Exception e) { ShowMessage(数据源连接失败请检查 ODBC 是否建好 e.Message); } } return Conn; }ProviderMSDASQL.1 是微软的 OLE DB 到 ODBC 的桥接层走它才能用上 ODBC 里建好的 DSNData Source 后面跟的是数据源名不是 .mdb 文件路径这是最常见的混淆点写成文件路径一样能连但换目录就断LoginPrompt 设 false 是为了避免每次操作弹一次框Access 数据库本来就没账号密码弹出来纯属浪费时间。4.2 登录密码校验和角色分流写在一次查询里指导书的字段清单里只有一个登陆密码没写权限区分但注册的学生和开课的老师显然是两类人。我把登录做成先查学生表、查不到再查教师表命中后把角色和 ID 存到全局变量里后续窗体按角色决定显示哪些按钮。// UnitMain.cpp —— 登录返回角色0 失败1 学生2 教师 int TFormMain::DoLogin(const AnsiString account, const AnsiString pwd) { TADOQuery *q new TADOQuery(NULL); q-Connection DM-GetConn(); q-SQL-Text SELECT 学号, 姓名 FROM 学生 WHERE 学号 :a AND 密码 :p; q-Parameters-ParamByName(a)-Value account; q-Parameters-ParamByName(p)-Value pwd; q-Open(); if (!q-Eof) { // 学生命中 gUserID q-FieldByName(学号)-AsString; gUserName q-FieldByName(姓名)-AsString; q-Close(); return 1; } q-Close(); q-SQL-Text SELECT 教师号, 教师姓名 FROM 教师 WHERE 教师号 :a AND 密码 :p; q-Parameters-ParamByName(a)-Value account; q-Parameters-ParamByName(p)-Value pwd; q-Open(); if (!q-Eof) { gUserID q-FieldByName(教师号)-AsString; gUserName q-FieldByName(教师姓名)-AsString; q-Close(); return 2; } q-Close(); return 0; }参数化查询是这段代码最重要的部分用 :a 占位再赋值账号框里输入什么都不会拼进 SQL 文本。课程设计报告里的安全章节经常没东西可写把这一点写进去需求、设计、测试三处都能对得上。密码明文存储在这个场景下可以接受因为它就是个教学系统但如果要二次开发成能演示的项目必须先换成哈希这一点放到最后一章说。4.3 成绩录入与查询总评成绩的算法该放在哪一层指导书的字段里有考试成绩、平时成绩、总评成绩三个如果三个都存进表录入界面就得算一次教师改平时成绩的时候还要记得重算总评一忘就出不一致。我的做法是只存前两个总评在 SQL 里现算比例写成常量。// UnitScore.cpp —— 按开课号查成绩总评按 7:3 现算 void TFormScore::LoadScore(const AnsiString openId) { TADOQuery *q new TADOQuery(NULL); q-Connection DM-GetConn(); q-SQL-Text SELECT s.学号, s.姓名, c.考试成绩, c.平时成绩, ROUND(c.考试成绩 * 0.7 c.平时成绩 * 0.3, 0) AS 总评 FROM 选课 c INNER JOIN 学生 s ON s.学号 c.学号 WHERE c.课号 :oid ORDER BY c.学号; q-Parameters-ParamByName(oid)-Value openId; q-Open(); DBGrid1-DataSource-DataSet q; // 直接挂到表格控件上 }ROUND 到整数是课程设计的简化处理真实场景一般保留一位小数并按绩点换算。总评放查询层的好处是改比例只动一处坏处是没法对总评建索引、不能按总评排序分页数据量大了要换成物化字段加触发器。这个权衡本身就可以写进报告的设计章节评审看的就是你有没有意识到取舍。5. 避坑与排查这套老工具链最容易翻车的五个地方5.1 环境与连接类数据源明明建了程序还是说连不上第一条现象是 BDE 里测试连接字体不变黑或者程序启动就弹「未找到数据源名称」。原因通常有两个。一是位数不匹配你在 64 位 ODBC 管理器里建了数据源程序是 32 位进程两边各看各的互相不认识。二是 BDE 别名建了但没 Apply配置停在内存里没落盘重启后消失。解决顺序固定下来先从 SysWOW64 目录进 ODBC 管理器确认数据源存在且能看到 Access 驱动再进 BDE 确认别名存在、ODBC DSN 那一栏填的和数据源名逐字一致右键 Apply 之后再右键 Open 测一次。三条都过了还连不上就去查 .mdb 文件是不是被 Access 独占打开了文件锁也会表现为连不上。第二条现象是编译一切正常运行时弹出「Cannot open database」路径看着没问题。原因多半是相对路径的参照点变了。C Builder 的 IDE 运行时工作目录是工程目录双击 exe 运行时工作目录是 exe 所在目录两者不同用相对路径找 .mdb 就会一头能跑一头报错。解决办法是把连接串里的数据源名跟路径解耦永远通过 ODBC 数据源间接寻址DSN 里存的是绝对路径程序只认名字。另外在报告里把「ODBC 数据源名」写成部署步骤的一部分换个机器第一件事就是建同名数据源比改代码可靠。5.2 数据与编码类中文变成问号字段长度也不够第三条现象是界面里录入的中文姓名存进 Access 以后变成乱码或者问号用 Access 直接打开又是好的。原因是字符集在链路上被转了两次。C Builder 6.0 的 AnsiString 按系统代码页存储简体中文环境下是 GBKODBC 驱动和 OLE DB 桥接层默认按 ANSI 传Access 2000 的 .mdb 本身也是 ANSI 编码。三层只要有一层被当成别的代码页解释中文就废了。解决方式不是去改驱动而是把数据库和系统代码页统一到 GBK避免在中间任何一层做转换字段类型用 TEXT 明确带长度别用「备注」类型备注字段在 ODBC 下读写的行为跟普通文本不一样中文尤其容易出问题。建表和录入都在同一台中文系统上完成中途别换语言环境。第四条现象是录入姓名超过十个字被截断或者插入直接报字段太小。原因是 Jet 的 TEXT(n) 里 n 是字符数但 AnsiString 在某些控件里按字节截一个汉字两字节视觉上没超、实际已经超了。解决办法是界面输入框加 MaxLength 限制并同步到字段长度建表时把中文类字段的长度按两倍预留姓名给 20、地址给 50比抠着算省心。5.3 逻辑与交付类人数超了、报告写不满 20 组用例第五条现象是检查时教师端显示某门课选了 31 人人数限制写的是 30。原因有两种一种是选课代码写成了先查后改中间没有事务两次操作之间挤进了别的请求另一种是退课只删了记录没减计数或者减计数失败后没有回滚日积月累把计数抬高。解决方式前一章已经给过把带条件的 UPDATE 和 INSERT 包在同一个事务里异常一律回滚。另外建议在选课表上建联合主键让重复选课在数据库层就被挡住代码里少写一个「是否已选」的查询逻辑简单了出错面也小。定期跑一条核对 SQL把选课人数和选课表的实际行数比一遍对不上就说明某次操作漏了回滚。第六条不算坑但很多人栽在这报告要求至少 20 组不同的对象测试序列学生写到第 8 组就编不下去了。根子在用例是拍脑袋想出来的没有从需求和边界推导。我的做法是按功能点分片每个功能给正常流、异常流、边界值三类登录 4 组、选课 5 组、退课 3 组、查询 4 组、成绩录入 4 组刚好 20 组每组写清输入、预期、实际三列。这套用例表存成 CSV 放进工程目录后面改代码时按表回归一遍比凭记忆点界面靠谱。6. 二次开发把课程设计改成能拿出来讲的项目指导书给的这套东西跑通之后最值钱的动作不是加功能而是有方向地换掉它的短板。我给自己的改造路线分三步每步都能单独验证不会一次改崩。第一步换数据库。把 Access 换成 SQLite 或轻量服务型数据库连接层从 ODBC 改成原生驱动这一步能验证你对数据访问层解耦得够不够干净。判断标准很简单改完数据库业务代码的 .cpp 文件修改行数应该少于十行。如果到处都在改说明当初把 SQL 散落在各个窗体里了先把查询收敛到数据模块再动手。改造方向原实现建议替换验证方式数据存储Access 2000 .mdbSQLite 或 MySQL换驱动后 20 组用例全过密码存储明文比对加盐哈希库里看不到明文登录仍成功并发控制单机事务数据库行锁或乐观版本号两个客户端同时选最后一门课只成功一个排课校验无同教室同时间冲突检测制造冲突时提交被拒第二步补业务规则。原系统只保证人数不超没管同一学生选的两门课时间冲突、没管先修课没修过不能选后续课。这两条都能用一条 SQL 表达写进选课前的校验里时间冲突查开课表里时间字段是否重叠先修课查该学生是否已有先行课号的通过记录。加规则的同时要同步补测试用例20 组扩到 30 组报告里那份测试表就立体了。第三步提权改造。登录只做了一次身份判断界面按钮全亮着学生点得到教师端的成绩录入。常见做法是把角色判断收到菜单和按钮的 Enabled 属性上再加一层服务端校验——单机版没有服务端就在数据层每个写操作前再判一次角色防止绕过界面直接调函数。这里有个我踩过的习惯从那以后我每次改完老的课程设计都会先把 test/cases.csv 那张表导进一个临时脚本跑一遍再手动点三个最关键的界面登录失败、选最后一门课、退课后计数回落。手动三步花不到五分钟能挡住改动引入的大部分回归。别嫌土老工具链的编译反馈本来就慢靠它兜底比事后翻日志划算。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询