用C++抽象类Person模拟校园系统:从学生选课到教师工资管理的完整代码实现

张开发
2026/4/5 13:59:19 15 分钟阅读

分享文章

用C++抽象类Person模拟校园系统:从学生选课到教师工资管理的完整代码实现
用C抽象类构建校园管理系统从设计模式到多态实践在校园信息化建设的浪潮中面向对象编程(OOP)为我们提供了强大的工具来模拟现实世界的复杂关系。想象一下当你走进校园看到学生们选课、教师授课、行政人员管理——这些看似独立的行为背后其实都共享着人这一基本属性。这正是抽象类大显身手的场景。1. 抽象类Person的设计哲学抽象类在C中就像是一份未完成的蓝图它定义了所有派生类必须遵循的接口规范。对于校园管理系统而言Person类正是这样一个基础抽象。class Person { protected: unsigned int id; string name; unsigned int birthday; public: Person(unsigned int id, string name, unsigned int birthday) : id(id), name(name), birthday(birthday) {} virtual void printInfo() 0; // 纯虚函数 virtual ~Person() {} // 虚析构函数 };这个设计有几个关键点值得注意纯虚函数printInfo() 0强制所有派生类必须实现自己的信息打印方法保护成员将数据成员设为protected而非private允许派生类直接访问虚析构函数确保通过基类指针删除派生类对象时能正确调用派生类的析构函数为什么不用接口C没有像Java那样的interface关键字但通过只包含纯虚函数的抽象类可以达到类似效果。不过在实际系统设计中适度包含一些公共属性和方法往往更有实用价值。2. 学生与教师类的具体实现从Person派生出Student和Teacher类体现了is-a的继承关系。这种设计不仅符合现实世界的逻辑还能最大化代码复用。2.1 Student类的专业特性class Student : public Person { private: string major; double score; public: Student(unsigned int id, string name, unsigned int birthday, string major, double score) : Person(id, name, birthday), major(major), score(score) {} void printInfo() override { cout Student [id id , name name , bir birthday , major major , score fixed setprecision(1) score ] endl; } };学生类扩展了专业和成绩属性并实现了特定的信息打印格式。注意几点格式化输出使用fixed和setprecision(1)确保分数输出统一为一位小数override关键字C11引入的显式重写标记避免意外重载而非重写成员初始化列表高效地初始化基类和派生类成员2.2 Teacher类的工资管理class Teacher : public Person { private: string title; double salary; public: Teacher(unsigned int id, string name, unsigned int birthday, string title, double salary) : Person(id, name, birthday), title(title), salary(salary) {} void printInfo() override { cout Teacher [id id , name name , bir birthday , title title , salary fixed setprecision(1) salary ] endl; } void raiseSalary(double percentage) { salary * (1 percentage/100); } };教师类除了基本信息外还包含了职称和工资属性。特别添加了加薪方法raiseSalary展示了如何为特定派生类添加特有功能格式化一致性与Student类保持相同的数字输出格式3. 多态在校园系统中的应用多态是面向对象设计的精髓所在它允许我们通过基类接口操作各种派生类对象。在校园管理系统中这种特性尤为实用。3.1 对象创建工厂Person* createPerson(int type) { unsigned int id, birthday; string name, extraInfo; double numericValue; cin id name birthday extraInfo numericValue; switch(type) { case 0: return new Student(id, name, birthday, extraInfo, numericValue); case 1: return new Teacher(id, name, birthday, extraInfo, numericValue); default: return nullptr; } }这个工厂函数根据输入类型动态创建不同的Person对象。注意统一接口无论创建学生还是教师外部调用方式完全一致内存管理返回的是堆分配的对象指针调用者需负责释放3.2 多态集合管理vectorPerson* campus; campus.push_back(new Student(...)); campus.push_back(new Teacher(...)); for (auto person : campus) { person-printInfo(); // 自动调用正确的printInfo实现 }使用标准库的vector管理校园人员集合通过基类指针调用虚函数实现多态行为。关键优势类型无关的遍历无需知道具体类型即可处理所有人员扩展性强新增人员类型不影响现有遍历代码4. 实战中的问题与优化在实际编码过程中会遇到各种设计选择和性能考量。以下是几个典型场景4.1 内存管理最佳实践原始代码使用C风格的malloc和裸指针在现代C中可以有更安全的替代方案// 不推荐 Person** ptr (Person**)malloc(sizeof(Person*) * cnt); // 推荐使用vector和智能指针 vectorunique_ptrPerson people; people.emplace_back(make_uniqueStudent(...));对比表内存管理方式对比方式优点缺点裸指针malloc完全控制内存分配容易内存泄漏需手动释放unique_ptr自动内存管理所有权明确不能复制只能移动shared_ptr引用计数自动释放循环引用可能导致泄漏4.2 输入输出处理优化原始代码的输入处理存在几个可以改进的点错误处理添加输入验证防止非法数据更灵活的输入使用getline处理可能包含空格的姓名性能优化减少不必要的格式化操作改进后的输入片段cin type; cin.ignore(); // 清除之前留下的换行符 string line; getline(cin, line); istringstream iss(line); // 从iss中解析各个字段...4.3 设计模式的应用校园系统可以应用多种设计模式提升架构质量策略模式将不同的打印策略抽象出来允许运行时切换访问者模式为人员集合添加新操作而不修改类本身观察者模式实现学生选课与课程人数变化的自动通知例如策略模式的简单实现class PrintStrategy { public: virtual void print(const Person) 0; }; class JsonPrintStrategy : public PrintStrategy { void print(const Person p) override { // 实现JSON格式打印 } }; // 在Person类中添加 void setPrintStrategy(PrintStrategy* strategy);5. 系统扩展与实战应用一个完整的校园管理系统远不止人员信息管理让我们看看如何基于这个核心扩展更多功能。5.1 课程管理系统class Course { private: string code; string name; vectorStudent* enrolledStudents; Teacher* instructor; public: void enrollStudent(Student* s) { enrolledStudents.push_back(s); } void setInstructor(Teacher* t) { instructor t; } };这个简单的课程类展示了关联关系课程与学生、教师之间的多对一和一对多关系业务逻辑选课和分配教师的基本方法5.2 工资单生成系统void generatePayroll(const vectorPerson* people, ostream out) { for (auto p : people) { if (auto t dynamic_castTeacher*(p)) { out t-getName() , t-getSalary() \n; } } }使用dynamic_cast进行安全的向下转型注意类型检查dynamic_cast在失败时返回nullptr开闭原则新增人员类型不影响现有工资单生成逻辑5.3 数据持久化将人员信息保存到文件并读取void saveToFile(const vectorunique_ptrPerson people, const string filename) { ofstream out(filename); for (auto p : people) { p-save(out); // 需在Person体系中添加虚save方法 } }对应的虚方法virtual void save(ostream out) const { out typeid(*this).name() ; // 保存类型信息 out id name birthday; }6. 性能考量与高级技巧当系统规模扩大时性能成为关键考量。以下是几个优化方向6.1 对象池技术频繁创建销毁对象时可以使用对象池减少内存分配开销class PersonPool { private: stackPerson* pool; public: Person* acquire(int type) { if (pool.empty()) return createNew(type); auto p pool.top(); pool.pop(); return p; } void release(Person* p) { pool.push(p); } };6.2 缓存友好设计调整数据布局提高缓存命中率struct PersonData { unsigned id; unsigned birthday; char name[32]; }; vectorPersonData rawData; // 连续内存存储6.3 多线程安全为校园系统添加线程安全支持class ThreadSafeCampus { mutex mtx; vectorPerson* people; public: void addPerson(Person* p) { lock_guardmutex lock(mtx); people.push_back(p); } };7. 测试驱动开发实践确保系统可靠性的关键是通过全面的测试。以下是一个简单的测试框架示例#define ASSERT(condition) \ if (!(condition)) { \ cerr Assert failed: #condition endl; \ return false; \ } bool testStudentCreation() { Student s(123, Alice, 20000101, CS, 95.5); ASSERT(s.getId() 123); ASSERT(s.getName() Alice); // 更多断言... return true; } int main() { if (!testStudentCreation()) { cerr Student test failed! endl; } // 运行其他测试... }测试要点包括边界条件如空名字、0分成绩异常输入无效日期、负分数内存泄漏使用工具如Valgrind检查8. 现代C特性应用C11/14/17/20的新特性可以大幅提升代码质量和开发效率8.1 使用auto和范围forfor (auto person : campus) { person-printInfo(); }8.2 移动语义优化class Person { string name; public: Person(string name) : name(std::move(name)) {} };8.3 lambda表达式auto findTeacher [](const vectorPerson* people, const string name) { for (auto p : people) { if (auto t dynamic_castTeacher*(p)) { if (t-getName() name) return t; } } return static_castTeacher*(nullptr); };9. UML建模与系统设计良好的设计始于清晰的模型。校园系统的简化UML类图可能包含---------------- ---------------- ---------------- | 抽象 | | Student | | Teacher | | Person ||-----| | | | ---------------- ---------------- ---------------- | -id: unsigned | | -major: string | | -title: string | | -name: string | | -score: double | | -salary: double| | -birthday: uint| ---------------- ---------------- ---------------- | printInfo() | | printInfo() | | printInfo()0 | ---------------- | raiseSalary() | ---------------- ----------------这个模型展示了继承关系实线空心箭头抽象类抽象stereotype主要属性和方法可见性和签名10. 实际项目中的经验分享在实现这类系统时有几个常遇到的坑值得注意变量命名冲突如原始代码中的major_就是为了避免与可能的宏定义冲突格式化输出一致性问题不同编译器对浮点输出的处理可能有细微差异多态数组的删除必须通过基类虚析构函数才能正确释放派生类对象类型标识的维护除了dynamic_cast有时需要添加显式的类型枚举一个实用的调试技巧是在基类中添加运行时类型信息virtual string getType() const 0; // 在派生类中实现 string Student::getType() const override { return Student; }最后关于设计抽象层次的建议不要过度设计但也要为可能的扩展留好接口。在校园系统这个案例中未来可能需要添加Staff(职员)、Visitor(访客)等新角色良好的抽象基类设计能让这些扩展变得轻松自然。

更多文章