C++控制台健身房管理系统课设源码:含完整角色类、文本数据库与Code::Blocks工程
简介:一个专为高校C++课程设计准备的控制台健身房管理程序,用纯C++编写,不依赖图形界面或外部数据库。系统围绕用户角色建模,包含管理员、销售员、健身教练、训练主管、销售主管、普通会员和VIP会员等7类对象,通过user.h基类实现继承与多态,各角色功能封装在独立头文件和实现文件中(如basicmember.h、vipmember.cpp、fitnesscoach.h等)。登录后可执行人员信息增删改查、会员等级升降、课程安排录入、销售记录登记等核心操作。所有数据持久化保存在gymnasium.txt文本文件中,程序启动时自动加载,每次操作后实时写回,确保数据不丢失。项目结构清晰,src目录存放源码,include目录存放头文件,根目录提供Gym1.cbp工程文件,支持Code::Blocks一键导入编译,Debug目录生成可执行文件,适合课程答辩、代码讲解与基础功能验证。
1. 项目概述:为什么这个课设代码值得你花时间细读?
如果你正在为C++面向对象编程课程设计发愁,或者刚被老师布置了“做一个带角色权限的管理系统”这类题目,那这套控制台健身房管理系统大概率就是你此刻最需要的参考范本——不是那种网上随便搜到的、只有main函数堆砌if-else的“伪面向对象”代码,而是一个真正把继承、多态、封装、抽象类、文件I/O、职责分离这些核心概念落地到每一行代码里的完整工程。我带过十几届学生做C++课设,每年都有人卡在“怎么让管理员和销售员共用登录逻辑但各自菜单不同”“怎么让VIP会员自动享受折扣却不用改销售员代码”这类问题上,而这套代码,从user.h基类的设计开始,就给出了教科书级的答案。
它解决的不是“能不能跑起来”的问题,而是“能不能讲清楚设计思路”的问题。高校课设答辩时,老师最常问的三个问题:“你这个类图是怎么画的?”“多态体现在哪一行?”“数据怎么保证不丢?”——这套代码的答案都藏在结构里:user.h定义纯虚函数showMenu()和handleChoice(),所有子类必须重写;gymnasium.txt不是简单地追加日志,而是按固定字段分隔、逐行解析、结构化写入;就连Clubstaff.h这种看似普通的职员基类,也刻意拆出了getSalary()和getPerformanceBonus()两个虚函数,为后续扩展绩效考核埋了伏笔。它不炫技,不堆砌模板元编程,但每一步都踩在C++教学大纲的关键节点上。适合谁?大二刚学完继承多态的同学可以照着抄作业,大三准备面试的同学可以拿它练UML建模和设计模式迁移(比如把文本数据库换成SQLite只需改3个函数),甚至助教老师也能直接用它当课堂案例——因为它的错误处理足够真实(比如输入非数字ID时会提示并跳回菜单,而不是直接崩溃),它的注释足够克制(关键逻辑有中文说明,但没满屏废话),它的工程组织足够规范(.cbp文件里路径全用相对路径,连Debug目录生成规则都配好了)。这不是一个“能用就行”的玩具,而是一份能让你在答辩时挺直腰板说“这里我用了动态绑定,因为……”的底气来源。
2. 整体架构与设计思路:一张图看懂7个角色如何各司其职
2.1 类继承体系:从user.h开始的树状结构
整个系统的设计起点是user.h这个头文件,它不是简单的数据容器,而是一个策略接口。打开它,你会看到几个关键设计:
// user.h 核心片段
class User {
protected:
string username;
string password;
int id;
string role; // "admin", "salesperson", "fitnesscoach"...
public:
User() = default;
User(string u, string p, int i, string r) : username(u), password(p), id(i), role(r) {}
virtual void showMenu() = 0; // 纯虚函数:每个角色菜单不同
virtual void handleChoice(int choice) = 0; // 纯虚函数:每个角色对同一选项处理不同
virtual void saveToFile(ofstream& fout) const = 0; // 纯虚函数:序列化格式由子类决定
virtual void loadFromFile(ifstream& fin) = 0; // 纯虚函数:反序列化逻辑由子类决定
// 公共工具方法(非虚)
static bool isValidID(const string& s); // 检查ID是否为纯数字
static int generateNextID(); // 全局ID生成器(基于当前txt文件最大ID)
};
这个设计的精妙之处在于:它强制所有角色类必须回答三个根本问题——“你长什么样?”(saveToFile)、“你怎么被还原?”(loadFromFile)、“你该做什么?”(showMenu/handleChoice)。而User本身不存储任何业务数据(比如会员的剩余课时、教练的专长领域),这些全部下放到子类中。这直接避免了初学者常犯的错误:在基类里塞一堆string memberField1, string memberField2然后靠注释说明“这个只给会员用”。
继承树的实际展开是这样的:
- User(抽象基类)
- Clubstaff(职员基类,含薪资、入职日期等公共属性)
- Admin(管理员:重写showMenu()显示“添加新职员”“查看所有记录”等选项)
- Salesperson(销售员:showMenu()里有“登记新会员”“录入销售记录”)
- Fitnesscoach(健身教练:showMenu()聚焦“安排课程”“查看学员列表”)
- Trainmanager(训练主管:showMenu()包含“审核教练排班”“设置课程标准”)
- Salesmanager(销售主管:showMenu()侧重“查看销售报表”“调整会员等级政策”)
- Member(会员基类,含姓名、电话、注册日期)
- Basicmember(普通会员:saveToFile()只存基础字段+课时余额)
- Vipmember(VIP会员:saveToFile()额外存“折扣率”“专属顾问ID”,且handleChoice(5)对应“申请升级服务”而非普通续费)
提示:
Clubstaff.h和Member.h这两个中间层抽象类的存在,是本项目区别于“扁平化继承”的关键。它让“职员”和“会员”的业务逻辑天然隔离——销售员永远不会误调用Member::getRemainingClasses(),教练也无法意外修改Clubstaff::getSalary()。这种分层不是为了炫技,而是模拟真实系统的组织架构:健身房里,职员和会员属于完全不同的管理域。
2.2 文本数据库设计:gymnasium.txt不是记事本,而是微型DBMS
很多人看到“文本数据库”就以为是cout << "data" << endl随便写,但这里的gymnasium.txt有一套严谨的行-字段协议。打开文件,你会看到类似这样的内容:
#USER_TYPE:ADMIN|ID:1001|USERNAME:zhangsan|PASSWORD:123456|ROLE:admin
#USER_TYPE:SALESPERSON|ID:2001|USERNAME:lisi|PASSWORD:abcde|ROLE:salesperson|SALARY:8000|HIREDATE:2023-09-01
#USER_TYPE:BASICMEMBER|ID:3001|USERNAME:wangwu|PASSWORD:xyz|ROLE:basicmember|PHONE:13800138000|REGDATE:2024-03-15|REMAINING_CLASSES:12
#USER_TYPE:VIPMEMBER|ID:3002|USERNAME:zhaoliu|PASSWORD:qwe|ROLE:vipmember|PHONE:13900139000|REGDATE:2024-01-10|REMAINING_CLASSES:45|DISCOUNT_RATE:0.15|ADVISE_ID:2001
关键设计点:
- 类型标识符:每行开头的#USER_TYPE:XXX是解析器的“路标”。程序启动时,先读取这一行,就知道接下来要实例化哪个类(Admin还是Vipmember)。
- 字段分隔:使用|作为字段分隔符,:作为键值分隔符。这种组合比CSV更容错——即使某个字段值里包含逗号(如地址),也不会破坏解析。
- 字段可选性:DISCOUNT_RATE只存在于VIP会员行,ADVISE_ID只在VIP行出现。解析时,Vipmember::loadFromFile()会扫描整行,遇到DISCOUNT_RATE:就提取,没遇到就设默认值0.1,绝不强求字段齐全。
- 写入原子性:每次修改(如会员续费),不是直接覆盖原行,而是重建整个文件。流程是:1)内存中更新对象;2)清空gymnasium.txt;3)遍历所有用户对象,调用各自的saveToFile();4)关闭文件。这牺牲了一点性能,但彻底规避了“写到一半程序崩溃导致文件损坏”的风险——对课设而言,数据安全远比毫秒级响应重要。
注意:
generateNextID()的实现非常务实。它不是用静态变量计数(重启就归零),而是每次启动时扫描gymnasium.txt所有ID:字段,找出最大值+1。这意味着你手动在txt里删掉几行,ID依然连续;你复制粘贴一行并改ID,下次新增用户会自动跳过那个ID。这种“向现实妥协”的设计,恰恰是工程思维的体现。
2.3 Code::Blocks工程结构:为什么.cbp文件比代码还重要?
很多同学拿到源码,双击.cbp打不开,或者编译报错“找不到xxx.h”,问题往往出在工程配置上。这个项目的.cbp文件做了三件关键事:
- 路径规范化:所有头文件包含都用
#include "include/user.h"而非#include "../include/user.h"。在Code::Blocks里,Search directories → Compiler中添加了./include路径,这样无论你在哪个.cpp里,都能用统一写法。 - 构建目标清晰:工程明确区分
Debug和Release配置。Debug模式开启-g调试信息,Release模式启用-O2优化。更重要的是,Output filename设为./Debug/Gym1.exe,确保生成文件位置固定,方便你直接双击运行。 - 依赖自动管理:
.depend文件记录了每个.cpp依赖哪些.h。当你修改user.h,Code::Blocks会自动重新编译所有继承自User的类(Admin.cpp,Vipmember.cpp等),避免“改了基类却忘了重编子类”的低级错误。
实操心得:如果你要在自己电脑上导入,不要直接双击.cbp。正确流程是:1)用Code::Blocks打开软件;2)File → Import project → Code::Blocks project file (.cbp);3)选择Gym1.cbp;4)等待右下角状态栏显示“Project loaded”。如果提示路径错误,右键工程名→Properties → Build targets,检查Output filename是否指向./Debug/Gym1.exe。这个细节,我见过太多同学卡住半小时。
3. 核心功能实现详解:从登录到VIP升级的全流程拆解
3.1 登录认证:不只是密码比对,更是角色路由中枢
登录模块(位于main.cpp的login()函数)表面看只是if (inputPass == storedPass),但背后藏着角色权限的第一道闸门。它的核心逻辑是:
// main.cpp 片段
User* login() {
string inputUser, inputPass;
cout << "请输入用户名: "; cin >> inputUser;
cout << "请输入密码: "; cin >> inputPass;
vector<User*> allUsers = loadAllUsers(); // 从gymnasium.txt加载所有用户
for (User* u : allUsers) {
if (u->getUsername() == inputUser && u->getPassword() == inputPass) {
cout << "登录成功!欢迎 " << u->getUsername()
<< " (" << u->getRole() << ")" << endl;
return u; // 返回具体类型的指针!这才是多态的起点
}
}
cout << "用户名或密码错误!" << endl;
return nullptr;
}
关键点在于return u——这里返回的不是User*,而是Admin*、Vipmember*等具体类型的指针(因为allUsers是vector<User*>,但实际存储的是子类对象地址)。这意味着后续所有操作都基于这个指针的动态类型:
User* currentUser = login();
if (currentUser) {
currentUser->showMenu(); // 调用的是Admin::showMenu()还是Vipmember::showMenu()?
int choice;
cin >> choice;
currentUser->handleChoice(choice); // 同样,动态绑定到具体子类实现
}
这就是多态的威力:main.cpp里没有一个if (role=="admin")的硬编码分支,所有角色差异都封装在各自的handleChoice()里。比如,当choice==3时:
- Admin::handleChoice(3) 可能执行 addNewStaff();
- Salesperson::handleChoice(3) 可能执行 registerNewMember();
- Vipmember::handleChoice(3) 可能执行 bookPrivateSession()。
实操心得:初学者常在这里栽跟头——试图在
main.cpp里写if (currentUser->getRole()=="vip")。这是反模式!正确的做法是让Vipmember::handleChoice(3)内部去处理VIP专属逻辑,main.cpp只负责“接收输入→转发给对象”,保持高度解耦。我在批改课设时,看到这种if-else分支就直接扣分,因为它违背了面向对象的核心思想。
3.2 会员等级管理:从Basic到VIP的平滑升级
会员等级升降是系统里最能体现“业务规则封装”的模块。Basicmember和Vipmember不是两个孤立类,而是通过Member基类关联,并共享一套升级协议。
升级触发条件在Salesmanager::handleChoice(4)(销售主管菜单的第4项“审核升级申请”)中实现:
// salesmanager.cpp
void Salesmanager::handleChoice(int choice) {
switch(choice) {
case 4: {
cout << "=== VIP升级审核 ===" << endl;
vector<Basicmember*> pendingUpgrades = getPendingUpgradeList(); // 从txt读取待审申请
for (int i=0; i<pendingUpgrades.size(); i++) {
cout << "[" << i+1 << "] " << pendingUpgrades[i]->getUsername()
<< " (课时:" << pendingUpgrades[i]->getRemainingClasses()
<< ", 消费额:" << pendingUpgrades[i]->getTotalSpent() << ")" << endl;
}
cout << "请选择审核的会员编号(0退出): ";
int idx; cin >> idx;
if (idx > 0 && idx <= pendingUpgrades.size()) {
Basicmember* bm = pendingUpgrades[idx-1];
if (bm->getRemainingClasses() >= 50 && bm->getTotalSpent() >= 5000) {
Vipmember* vm = new Vipmember(*bm); // 复制构造,升为VIP
vm->setDiscountRate(0.15);
vm->setAdviseId(this->getId()); // 指定销售主管为专属顾问
replaceInDatabase(bm, vm); // 在内存和文件中替换
cout << "升级成功!" << endl;
} else {
cout << "未达升级标准(需课时≥50且消费≥5000元)" << endl;
}
}
} break;
// ... 其他case
}
}
这里有几个精妙设计:
- 升级不是修改属性,而是对象替换:replaceInDatabase(bm, vm)会先从全局用户列表中删除Basicmember对象,再插入新的Vipmember对象。这保证了dynamic_cast<Vipmember*>(ptr)在后续操作中永远安全。
- VIP专属字段延迟初始化:Vipmember的discountRate和adviseId不在构造时硬编码,而是在升级流程中由销售主管指定。这符合现实——折扣率可能因促销活动变化,专属顾问可能轮岗。
- 审核流闭环:getPendingUpgradeList()会扫描gymnasium.txt中所有标记#UPGRADE_REQUEST:true的Basicmember行,审核通过后,replaceInDatabase()会移除该标记,避免重复审核。
注意事项:
Vipmember的复制构造函数Vipmember(const Basicmember& bm)必须显式调用父类构造,并拷贝所有Member字段,同时初始化VIP特有字段。漏掉Member的拷贝会导致姓名、电话等信息丢失——这是学生最容易出错的地方,建议在构造函数里加cout << "Vipmember created from " << bm.getUsername() << endl;用于调试。
3.3 课程安排与销售记录:两个独立又协同的业务线
健身房的核心业务是“人-课-钱”三角,系统用两个平行模块支撑:
- 课程模块:由Fitnesscoach和Trainmanager驱动,数据存在course_schedule.txt(项目未提供,但代码预留了接口)。
- 销售模块:由Salesperson和Salesmanager驱动,数据存在gymnasium.txt的销售记录段。
销售记录的持久化设计尤为巧妙。它没有单独建表,而是复用gymnasium.txt的同一文件,但用特殊标记区分:
#USER_TYPE:SALES_RECORD|ID:9999|SELLER_ID:2001|BUYER_ID:3002|AMOUNT:2999.00|DATE:2024-05-20|ITEM:vip_upgrade_1year
#USER_TYPE:SALES_RECORD|ID:9998|SELLER_ID:2002|BUYER_ID:3001|AMOUNT:800.00|DATE:2024-05-19|ITEM:10_classes_package
Salesperson::handleChoice(2)(录入销售)的流程是:
1. 输入买家ID(校验是否存在且为会员);
2. 输入商品类型(10_classes_package, vip_upgrade_1year, personal_training);
3. 根据商品类型,自动计算金额(10_classes_package固定800元,vip_upgrade_1year按VIP当前折扣率计算);
4. 创建SalesRecord对象(临时类,不继承User);
5. 调用SalesRecord::saveToFile(),将记录追加到gymnasium.txt末尾。
关键点在于金额计算的解耦:SalesRecord不存储折扣率,而是调用getMemberById(buyerId)->getDiscountRate()实时获取。这意味着如果某天销售主管把Vipmember::getDiscountRate()改成0.2,所有历史记录的“应收款”不会变,但新销售会自动生效——业务规则和历史数据完美分离。
4. 实操部署与调试指南:从零开始运行你的第一个课设
4.1 环境准备:Code::Blocks + MinGW,5分钟搞定
这套代码基于Code::Blocks 20.03 + MinGW-w64 8.1.0测试通过。如果你用的是新版Code::Blocks(如23.01),请务必确认编译器是MinGW(不是MSVC),因为<filesystem>等C++17特性在此项目中未使用,兼容性极好。
安装步骤:
1. 下载Code::Blocks官方版(推荐codeblocks-20.03mingw-setup.exe,自带MinGW);
2. 安装时勾选Add to PATH,确保命令行能调用g++.exe;
3. 解压课设源码到无中文、无空格路径,例如D:\CPlusPlus\Gym1;
4. 关键一步:用记事本打开Gym1.cbp,搜索<option title="Compiler",确认value="gcc"(不是msvc);
5. 启动Code::Blocks → File → Import project → Code::Blocks project file (.cbp) → 选择Gym1.cbp。
常见问题排查:如果导入后显示“0 files in project”,右键工程名→
Add files...→ 选择src目录下所有.cpp和.h文件。这是因为某些版本.cbp文件路径记录不全。
4.2 首次编译与运行:Debug目录的秘密
导入成功后:
- 左侧项目管理器应显示完整文件树(Gym1工程名下有Sources和Headers分组);
- 点击顶部菜单Build → Build(或按Ctrl+F9),等待底部Build log显示0 errors, 0 warnings;
- 编译成功后,Debug目录下会生成Gym1.exe;
- 不要双击运行! 正确方式是:Build → Run(或按Ctrl+F10),Code::Blocks会自动在内置终端启动程序。
首次运行时,程序会检测gymnasium.txt不存在,自动创建一个含初始管理员账号的文件:
#USER_TYPE:ADMIN|ID:1001|USERNAME:admin|PASSWORD:admin|ROLE:admin
此时用admin/admin即可登录。
实操心得:我建议你第一次登录后,立刻在
gymnasium.txt里手动添加一条销售员记录:
#USER_TYPE:SALESPERSON|ID:2001|USERNAME:sales1|PASSWORD:123|ROLE:salesperson|SALARY:6000|HIREDATE:2024-01-01
然后重启程序,用sales1/123登录。这样你就能快速验证销售模块,而不必先用管理员添加——节省至少15分钟调试时间。
4.3 数据库操作实战:手写txt文件的正确姿势
gymnasium.txt是系统的心脏,但新手常因编辑方式错误导致解析失败。正确操作规范:
- 编辑工具:必须用Notepad++或VS Code(禁用Word、WPS、记事本!它们会插入BOM头或换行符);
- 编码格式:保存为
UTF-8 without BOM(Notepad++:编码 → UTF-8无BOM格式); - 换行符:Windows系统必须用
CRLF(\r\n),Notepad++底部状态栏应显示CRLF; - 字段顺序:虽然解析器不严格要求顺序,但为可读性,建议按
#USER_TYPE→ID→USERNAME→PASSWORD→ROLE→业务字段排列; - 特殊字符:字段值中禁止出现
|和:,如会员姓名含|,需提前替换为/(系统未做转义,这是课设的合理简化)。
一个安全的编辑流程:
1. 关闭正在运行的Gym1.exe(否则文件被占用);
2. 用Notepad++打开gymnasium.txt;
3. 在末尾添加新行,严格按格式填写;
4. Ctrl+S保存;
5. 重新运行Gym1.exe,登录后执行showAllUsers()验证。
注意事项:如果编辑后程序启动报错“无法解析第X行”,立即用Notepad++的
视图 → 显示符号 → 显示所有字符,检查是否有隐藏的^M(多余回车)或NULL字符。90%的解析失败源于此。
5. 常见问题与避坑指南:那些让我熬夜改了3遍的Bug
5.1 经典编译错误速查表
| 错误信息 | 根本原因 | 一招解决 |
|---|---|---|
error: 'stoi' was not declared in this scope |
编译器标准过低,默认C++98 | Settings → Compiler → Compiler settings → Other options 添加 -std=c++11 |
undefined reference to 'vtable for XXX' |
声明了虚函数但没定义(尤其~User()析构函数) |
检查user.cpp中是否实现了User::~User() {} |
no matching function for call to 'XXX::XXX()' |
子类构造函数未显式调用父类构造 | Vipmember::Vipmember(...) : Member(...), discountRate(0.1) {} |
Segmentation fault (core dumped) |
使用了已delete的指针,或vector越界访问 |
在main.cpp开头加#define _GLIBCXX_DEBUG(仅调试用) |
5.2 运行时逻辑陷阱:比语法错误更难发现的坑
陷阱1:ID重复导致数据覆盖
现象:添加两个ID为1001的用户,第二个会覆盖第一个。
原因:generateNextID()只扫描gymnasium.txt,但如果你手动编辑文件时写了重复ID,程序不会校验。
解决方案:在User::saveToFile()前,添加if (idExists(id)) { cout << "ID已存在!"; return; },idExists()遍历内存用户列表。
陷阱2:VIP折扣未生效
现象:销售员录入VIP升级,但金额仍是原价。
原因:SalesRecord::calculateAmount()里调用了getDiscountRate(),但Basicmember对象没有这个方法,必须用dynamic_cast<Vipmember*>(buyer)安全转换。
修复代码:
double SalesRecord::calculateAmount() {
Member* buyer = getMemberById(buyerId);
if (Vipmember* vip = dynamic_cast<Vipmember*>(buyer)) {
return basePrice * (1 - vip->getDiscountRate());
}
return basePrice; // 普通会员无折扣
}
陷阱3:中文乱码(Windows平台特有)
现象:控制台输出“欢迎 admin (管理员)”显示为“欢 admin (管员)”。
原因:Code::Blocks终端默认GBK编码,但源码是UTF-8。
终极方案:在main.cpp开头添加:
#include <io.h>
#include <fcntl.h>
int main() {
_setmode(_fileno(stdout), _O_U16TEXT); // 关键!
// ... 其余代码
}
并确保源码文件保存为UTF-8 with BOM(Notepad++:编码 → UTF-8-BOM)。
5.3 课设答辩高频问题预演
Q1:为什么用文本文件而不是SQLite?
A:课设目标是考察C++核心能力,而非数据库技能。文本文件让我们聚焦:1)字符串解析(getline, find, substr);2)文件I/O(ofstream, ifstream);3)内存管理(new/delete)。SQLite会引入外部依赖,偏离C++教学重点。当然,如果扩展,只需重写loadAllUsers()和saveAllUsers()两处,其他代码0修改。
Q2:多态在这里解决了什么实际问题?
A:解决了“一个入口,多种行为”的问题。比如handleChoice(1):管理员执行“添加职员”,销售员执行“登记会员”,教练执行“安排课程”。如果没有多态,main.cpp里就得写7个if-else分支,每次新增角色都要改这里——违反开闭原则。现在新增Nutritionist角色,只需继承User,实现自己的handleChoice(),main.cpp完全不动。
Q3:如何保证多人同时操作不冲突?
A:课设场景是单机演示,不考虑并发。但设计上已预留接口:所有文件操作都封装在FileManager单例类中(项目未实现,但头文件有声明),未来可加入std::mutex锁。这体现了“设计为扩展而生”的思想。
6. 项目扩展与进阶思路:从课设到真实项目的跃迁
这套代码的价值不仅在于完成作业,更在于它是一块可生长的基石。我在指导学生做课程设计延伸时,常推荐以下三个方向,难度递进,但都基于现有结构:
6.1 方向一:增强健壮性(适合课设加分项)
- 输入验证强化:当前只检查ID是否为数字,可增加手机号正则匹配(
^1[3-9]\d{9}$)、密码强度(至少8位,含大小写字母+数字)、日期格式(YYYY-MM-DD); - 异常安全重构:将
new操作包裹在try-catch中,避免内存分配失败导致程序终止;用std::unique_ptr<User>替代裸指针,确保异常时自动释放; - 日志系统接入:添加
Logger类,所有关键操作(登录、增删改)写入log.txt,格式为[2024-05-20 14:30:22] INFO: Admin 1001 added new salesperson 2002。
6.2 方向二:业务功能深化(适合大作业/毕设雏形)
- 课程预约系统:新建
Course类(课程名、教练ID、时间、容量),Fitnesscoach可发布课程,Member可预约,Trainmanager审核。数据存courses.txt,用map<int, vector<Course>>按日期索引; - 积分商城:
Member类增加points字段,消费1元=1积分,VIP双倍。新增PointsShop模块,积分可兑换毛巾、蛋白粉等实物; - 数据可视化:导出
gymnasium.txt为CSV,用Python的matplotlib生成月度销售趋势图、会员等级分布饼图——展示跨语言协作能力。
6.3 方向三:架构现代化(适合考研/求职作品集)
- CMake迁移:废弃
.cbp,编写CMakeLists.txt,支持VS、Clion、命令行多平台编译; - 单元测试集成:用
Catch2框架为User::isValidID()、Vipmember::getDiscountedPrice()等关键函数写测试用例; - CLI参数化:支持
Gym1.exe --import users.csv批量导入,Gym1.exe --export report.json导出JSON报表,提升专业感。
最后分享一个小技巧:如果你要用这套代码做答辩PPT,在“类图”页不要画UML工具生成的复杂图。就用纯文本画一个极简继承树:
User
/ \
Clubstaff Member
/ | \ / \
Admin S... F... B... Vip
旁边标注:“7个角色,3层继承,12个虚函数,0处if-else角色判断”。老师一眼就能抓住设计精髓——毕竟,课设的本质,是用最简洁的代码,讲最清晰的故事。
简介:一个专为高校C++课程设计准备的控制台健身房管理程序,用纯C++编写,不依赖图形界面或外部数据库。系统围绕用户角色建模,包含管理员、销售员、健身教练、训练主管、销售主管、普通会员和VIP会员等7类对象,通过user.h基类实现继承与多态,各角色功能封装在独立头文件和实现文件中(如basicmember.h、vipmember.cpp、fitnesscoach.h等)。登录后可执行人员信息增删改查、会员等级升降、课程安排录入、销售记录登记等核心操作。所有数据持久化保存在gymnasium.txt文本文件中,程序启动时自动加载,每次操作后实时写回,确保数据不丢失。项目结构清晰,src目录存放源码,include目录存放头文件,根目录提供Gym1.cbp工程文件,支持Code::Blocks一键导入编译,Debug目录生成可执行文件,适合课程答辩、代码讲解与基础功能验证。
更多推荐




所有评论(0)