实验4 组合与继承
任务一:
源代码:
GradeCalc.hpp
1 #pragma once 2 3 #include <vector> 4 #include <array> 5 #include <string> 6 7 class GradeCalc { 8 public: 9 GradeCalc(const std::string &cname); 10 void input(int n); // 录入n个成绩 11 void output() const; // 输出成绩 12 void sort(bool ascending = false); // 排序 (默认降序) 13 int min() const; // 返回最低分(如成绩未录入,返回-1) 14 int max() const; // 返回最高分 (如成绩未录入,返回-1) 15 double average() const; // 返回平均分 (如成绩未录入,返回0.0) 16 void info(); // 输出课程成绩信息 17 18 private: 19 void compute(); // 成绩统计 20 21 private: 22 std::string course_name; // 课程名 23 std::vector<int> grades; // 课程成绩 24 std::array<int, 5> counts; // 保存各分数段人数([0, 60), [60, 70), [70, 80), [80, 90), [90, 100] 25 std::array<double, 5> rates; // 保存各分数段人数占比 26 bool is_dirty; // 脏标记,记录是否成绩信息有变更 27 };
GradeCalc.cpp
1 #include <algorithm> 2 #include <array> 3 #include <cstdlib> 4 #include <iomanip> 5 #include <iostream> 6 #include <numeric> 7 #include <string> 8 #include <vector> 9 10 #include "GradeCalc.hpp" 11 12 GradeCalc::GradeCalc(const std::string &cname):course_name{cname},is_dirty{true} { 13 counts.fill(0); 14 rates.fill(0); 15 } 16 17 void GradeCalc::input(int n) { 18 if(n < 0) { 19 std::cerr << "无效输入! 人数不能为负数\n"; 20 std::exit(1); 21 } 22 23 grades.reserve(n); 24 25 int grade; 26 27 for(int i = 0; i < n;) { 28 std::cin >> grade; 29 30 if(grade < 0 || grade > 100) { 31 std::cerr << "无效输入! 分数须在[0,100]\n"; 32 continue; 33 } 34 35 grades.push_back(grade); 36 ++i; 37 } 38 39 is_dirty = true; // 设置脏标记:成绩信息有变更 40 } 41 42 void GradeCalc::output() const { 43 for(auto grade: grades) 44 std::cout << grade << ' '; 45 std::cout << std::endl; 46 } 47 48 void GradeCalc::sort(bool ascending) { 49 if(ascending) 50 std::sort(grades.begin(), grades.end()); 51 else 52 std::sort(grades.begin(), grades.end(), std::greater<int>()); 53 } 54 55 int GradeCalc::min() const { 56 if(grades.empty()) 57 return -1; 58 59 auto it = std::min_element(grades.begin(), grades.end()); 60 return *it; 61 } 62 63 int GradeCalc::max() const { 64 if(grades.empty()) 65 return -1; 66 67 auto it = std::max_element(grades.begin(), grades.end()); 68 return *it; 69 } 70 71 double GradeCalc::average() const { 72 if(grades.empty()) 73 return 0.0; 74 75 double avg = std::accumulate(grades.begin(), grades.end(), 0.0)/grades.size(); 76 return avg; 77 } 78 79 void GradeCalc::info() { 80 if(is_dirty) 81 compute(); 82 83 std::cout << "课程名称:\t" << course_name << std::endl; 84 std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() << std::endl; 85 std::cout << "最高分:\t" << max() << std::endl; 86 std::cout << "最低分:\t" << min() << std::endl; 87 88 const std::array<std::string, 5> grade_range{"[0, 60) ", 89 "[60, 70)", 90 "[70, 80)", 91 "[80, 90)", 92 "[90, 100]"}; 93 94 for(int i = static_cast<int>(grade_range.size())-1; i >= 0; --i) 95 std::cout << grade_range[i] << "\t: " << counts[i] << "人\t" 96 << std::fixed << std::setprecision(2) << rates[i]*100 << "%\n"; 97 } 98 99 void GradeCalc::compute() { 100 if(grades.empty()) 101 return; 102 103 counts.fill(0); 104 rates.fill(0.0); 105 106 // 统计各分数段人数 107 for(auto grade:grades) { 108 if(grade < 60) 109 ++counts[0]; // [0, 60) 110 else if (grade < 70) 111 ++counts[1]; // [60, 70) 112 else if (grade < 80) 113 ++counts[2]; // [70, 80) 114 else if (grade < 90) 115 ++counts[3]; // [80, 90) 116 else 117 ++counts[4]; // [90, 100] 118 } 119 120 // 统计各分数段比例 121 for(size_t i = 0; i < rates.size(); ++i) 122 rates[i] = counts[i] * 1.0 / grades.size(); 123 124 is_dirty = false; // 更新脏标记 125 }
demo1.cpp
1 #include <iostream> 2 #include <string> 3 #include "GradeCalc.hpp" 4 5 void test() { 6 GradeCalc c1("OOP"); 7 8 std::cout << "录入成绩:\n"; 9 c1.input(5); 10 11 std::cout << "输出成绩:\n"; 12 c1.output(); 13 14 std::cout << "排序后成绩:\n"; 15 c1.sort(); c1.output(); 16 17 std::cout << "*************成绩统计信息*************\n"; 18 c1.info(); 19 20 } 21 22 int main() { 23 test(); 24 }
运行结果:

问题1:
std::string course_name:存储课程名称。
std::vector<int> grades:存储所有学生的成绩。
std::array<int, 5> counts:存储五个分数段([0,60)、[60,70)、[70,80)、[80,90)、[90,100])的人数。
std::array<double, 5> rates:存储五个分数段的人数比例。
bool is_dirty:标记成绩数据是否被修改,用于决定是否重新计算统计信息。
问题2:
不合法。
GradeCalc 类中没有名为 push_back 的公有成员函数
grades 成员是 private 的,不能在类外部直接访问
即使能访问,grades 是 std::vector<int> 类型,应该调用其 push_back 方法,但这是通过类内部的实现细节
问题3:
(1)调用1次; is_dirty的作用:作为标记,用于避免重复计算。当成绩数据发生变化时设置为true,在compute计算完成后设置为false。这样在连续调用info()时,只有第一次需要重新计算,后续调用直接使用已计算的结果。
(2)不需要,
当前的设计采用了"惰性计算"模式,compute()只在需要显示统计信息且数据有变化时才被调用,新增update_grade()时,只需在方法内部设置 is_dirty = true 即可
问题4:
在 compute() 函数中添加:
1 void GradeCalc::compute() { 2 // 原有统计代码... 3 4 // 新增中位数计算 5 std::vector<int> temp = grades; 6 std::sort(temp.begin(), temp.end()); 7 8 int mid = temp.size() / 2; 9 if(temp.size() % 2 == 0) { 10 median_value = (temp[mid-1] + temp[mid]) / 2.0; 11 } else { 12 median_value = temp[mid]; 13 } 14 15 is_dirty = false; 16 }
问题5:
不能去掉
当成绩数据被修改(如删除部分成绩)后,如果不重置counts和rates,会导致统计错误
问题6:
(1)没有功能影响
(2)有性能影响,去掉reserve会导致vector在push_back时可能发生多次内存重新分配和数据拷贝,增加了时间开销。
任务2:
源代码:
GradeCalc.hpp
1 #pragma once 2 3 #include <array> 4 #include <string> 5 #include <vector> 6 7 class GradeCalc: private std::vector<int> { 8 public: 9 GradeCalc(const std::string &cname); 10 void input(int n); // 录入n个成绩 11 void output() const; // 输出成绩 12 void sort(bool ascending = false); // 排序 (默认降序) 13 int min() const; // 返回最低分 14 int max() const; // 返回最高分 15 double average() const; // 返回平均分 16 void info(); // 输出成绩统计信息 17 18 private: 19 void compute(); // 计算成绩统计信息 20 21 private: 22 std::string course_name; // 课程名 23 std::array<int, 5> counts; // 保存各分数段人数([0, 60), [60, 70), [70, 80), [80, 90), [90, 100] 24 std::array<double, 5> rates; // 保存各分数段占比 25 bool is_dirty; // 脏标记,记录是否成绩信息有变更 26 };
GradeCalc.cpp
1 #include <algorithm> 2 #include <array> 3 #include <cstdlib> 4 #include <iomanip> 5 #include <iostream> 6 #include <numeric> 7 #include <string> 8 #include <vector> 9 #include "GradeCalc.hpp" 10 11 12 GradeCalc::GradeCalc(const std::string &cname): course_name{cname}, is_dirty{true}{ 13 counts.fill(0); 14 rates.fill(0); 15 } 16 17 void GradeCalc::input(int n) { 18 if(n < 0) { 19 std::cerr << "无效输入! 人数不能为负数\n"; 20 return; 21 } 22 23 this->reserve(n); 24 25 int grade; 26 27 for(int i = 0; i < n;) { 28 std::cin >> grade; 29 if(grade < 0 || grade > 100) { 30 std::cerr << "无效输入! 分数须在[0,100]\n"; 31 continue; 32 } 33 34 this->push_back(grade); 35 ++i; 36 } 37 38 is_dirty = true; 39 } 40 41 void GradeCalc::output() const { 42 for(auto grade: *this) 43 std::cout << grade << ' '; 44 std::cout << std::endl; 45 } 46 47 void GradeCalc::sort(bool ascending) { 48 if(ascending) 49 std::sort(this->begin(), this->end()); 50 else 51 std::sort(this->begin(), this->end(), std::greater<int>()); 52 } 53 54 int GradeCalc::min() const { 55 if(this->empty()) 56 return -1; 57 58 return *std::min_element(this->begin(), this->end()); 59 } 60 61 int GradeCalc::max() const { 62 if(this->empty()) 63 return -1; 64 65 return *std::max_element(this->begin(), this->end()); 66 } 67 68 double GradeCalc::average() const { 69 if(this->empty()) 70 return 0.0; 71 72 double avg = std::accumulate(this->begin(), this->end(), 0.0) / this->size(); 73 return avg; 74 } 75 76 void GradeCalc::info() { 77 if(is_dirty) 78 compute(); 79 80 std::cout << "课程名称:\t" << course_name << std::endl; 81 std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() << std::endl; 82 std::cout << "最高分:\t" << max() << std::endl; 83 std::cout << "最低分:\t" << min() << std::endl; 84 85 const std::array<std::string, 5> grade_range{"[0, 60) ", 86 "[60, 70)", 87 "[70, 80)", 88 "[80, 90)", 89 "[90, 100]"}; 90 91 for(int i = static_cast<int>(grade_range.size())-1; i >= 0; --i) 92 std::cout << grade_range[i] << "\t: " << counts[i] << "人\t" 93 << std::fixed << std::setprecision(2) << rates[i]*100 << "%\n"; 94 } 95 96 void GradeCalc::compute() { 97 if(this->empty()) 98 return; 99 100 counts.fill(0); 101 rates.fill(0); 102 103 // 统计各分数段人数 104 for(int grade: *this) { 105 if(grade < 60) 106 ++counts[0]; // [0, 60) 107 else if (grade < 70) 108 ++counts[1]; // [60, 70) 109 else if (grade < 80) 110 ++counts[2]; // [70, 80) 111 else if (grade < 90) 112 ++counts[3]; // [80, 90) 113 else 114 ++counts[4]; // [90, 100] 115 } 116 117 // 统计各分数段比例 118 for(size_t i = 0; i < rates.size(); ++i) 119 rates[i] = counts[i] * 1.0 / this->size(); 120 121 is_dirty = false; 122 }
demo2.cpp
1 #include <iostream> 2 #include <string> 3 #include "GradeCalc.hpp" 4 5 void test() { 6 GradeCalc c1("OOP"); 7 8 std::cout << "录入成绩:\n"; 9 c1.input(5); 10 11 std::cout << "输出成绩:\n"; 12 c1.output(); 13 14 std::cout << "排序后成绩:\n"; 15 c1.sort(); c1.output(); 16 17 std::cout << "*************成绩统计信息*************\n"; 18 c1.info(); 19 20 } 21 22 int main() { 23 test(); 24 }
运行结果:

问题1:
class GradeCalc: private std::vector<int>
问题2:
不会,由于是私有继承(private inheritance),std::vector<int>的所有成员在GradeCalc中都成为私有成员,外部代码无法直接访问
不合法,私有继承使得push_back()方法在GradeCalc类中成为私有成员,外部代码无法调用。
问题3:
组合方式:直接访问内部的 grades 成员变量,这是对具体实现的直接依赖
继承方式:通过 *this 利用继承来的迭代器接口访问数据,这是对抽象接口的依赖
问题4:
组合;组合方案更好地体现了"最小接口原则",只为用户提供必要的成绩管理功能。
任务3:
源代码:
Graph.hpp
1 #pragma once 2 3 #include <string> 4 #include <vector> 5 6 enum class GraphType {circle, triangle, rectangle}; 7 8 // Graph类定义 9 class Graph { 10 public: 11 virtual void draw() {} 12 virtual ~Graph() = default; 13 }; 14 15 // Circle类声明 16 class Circle : public Graph { 17 public: 18 void draw(); 19 }; 20 21 // Triangle类声明 22 class Triangle : public Graph { 23 public: 24 void draw(); 25 }; 26 27 // Rectangle类声明 28 class Rectangle : public Graph { 29 public: 30 void draw(); 31 }; 32 33 // Canvas类声明 34 class Canvas { 35 public: 36 void add(const std::string& type); // 根据字符串添加图形 37 void paint() const; // 使用统一接口绘制所有图形 38 ~Canvas(); // 手动释放资源 39 40 private: 41 std::vector<Graph*> graphs; 42 }; 43 44 // 4. 工具函数 45 GraphType str_to_GraphType(const std::string& s); // 字符串转枚举类型 46 Graph* make_graph(const std::string& type); // 创建图形,返回堆对象指针
Graph.cpp
1 #include <algorithm> 2 #include <cctype> 3 #include <iostream> 4 #include <string> 5 6 #include "Graph.hpp" 7 8 // Circle类实现 9 void Circle::draw() { std::cout << "draw a circle...\n"; } 10 11 // Triangle类实现 12 void Triangle::draw() { std::cout << "draw a triangle...\n"; } 13 14 // Rectangle类实现 15 void Rectangle::draw() { std::cout << "draw a rectangle...\n"; } 16 17 // Canvas类实现 18 void Canvas::add(const std::string& type) { 19 Graph* g = make_graph(type); 20 if (g) 21 graphs.push_back(g); 22 } 23 24 void Canvas::paint() const { 25 for (Graph* g : graphs) 26 g->draw(); 27 } 28 29 Canvas::~Canvas() { 30 for (Graph* g : graphs) 31 delete g; 32 } 33 34 // 工具函数实现 35 // 字符串 → 枚举转换 36 GraphType str_to_GraphType(const std::string& s) { 37 std::string t = s; 38 std::transform(s.begin(), s.end(), t.begin(), 39 [](unsigned char c) { return std::tolower(c);}); 40 41 if (t == "circle") 42 return GraphType::circle; 43 44 if (t == "triangle") 45 return GraphType::triangle; 46 47 if (t == "rectangle") 48 return GraphType::rectangle; 49 50 return GraphType::circle; // 缺省返回 51 } 52 53 // 创建图形,返回堆对象指针 54 Graph* make_graph(const std::string& type) { 55 switch (str_to_GraphType(type)) { 56 case GraphType::circle: return new Circle; 57 case GraphType::triangle: return new Triangle; 58 case GraphType::rectangle: return new Rectangle; 59 default: return nullptr; 60 } 61 }
demo3.cpp
1 #include <string> 2 #include "Graph.hpp" 3 4 void test() { 5 Canvas canvas; 6 7 canvas.add("circle"); 8 canvas.add("triangle"); 9 canvas.add("rectangle"); 10 canvas.paint(); 11 } 12 13 int main() { 14 test(); 15 }
运行结果:

问题1:
(1)
private: std::vector<Graph*> graphs;
被组合对象的功能:存储和管理指向各种图形对象(Graph)的指针,实现图形的集合管理。
(2)
class Circle : public Graph class Triangle : public Graph class Rectangle : public Graph
问题2:
(1)如果draw未声明为虚函数,g->draw() 将始终调用Graph基类的draw方法,所有图形都会显示为"draw a circle...",无法实现运行时多态。
(2)会导致派生类特有的信息丢失,无法实现多态,所有图形都会调用基类的draw方法而无法正确显示各自类型。
(3)删除图形对象时,只会清理"图形"的公共部分,而不会清理"圆形"、"三角形"等具体图形特有的部分,导致内存清理不干净。
问题3:
Graph.hpp:
在 enum class GraphType 中添加 star;
新增 class Star : public Graph 声明;
Graph.cpp:
实现 Star::draw() 方法;
在 str_to_GraphType() 函数中添加 "star" 字符串判断;
在 make_graph() 函数中添加 GraphType::star 分支;
问题4:
(1)在 Canvas 类的析构函数 ~Canvas() 中被释放
(2)使用简单,控制灵活,但极易导致内存泄漏和悬空指针,难以维护。
任务4:
问题描述
设计一个电子毛绒玩具系统,不同类型的玩具具有不同的特异功能,通过统一的接口进行管理。
对象关系设计
继承关系:各类玩具继承自基类Toy
组合关系:ToyFactory包含多个Toy对象
多态机制:通过虚函数实现统一接口
源代码
Toy.hpp:
1 #pragma once 2 #include <string> 3 #include <iostream> 4 #include <vector> 5 #include <memory> 6 7 // 玩具基类 8 class Toy { 9 protected: 10 std::string name; 11 std::string type; 12 int battery; 13 14 public: 15 Toy(const std::string& n, const std::string& t); 16 virtual ~Toy() = default; 17 18 virtual void specialAbility() = 0; // 特异功能 19 virtual void play(); // 玩耍 20 void charge(); // 充电 21 22 void showInfo() const; 23 std::string getName() const; 24 std::string getType() const; 25 }; 26 27 // 动物玩具类 28 class AnimalToy : public Toy { 29 private: 30 std::string animalSound; 31 32 public: 33 AnimalToy(const std::string& n, const std::string& sound); 34 void specialAbility() override; 35 void play() override; 36 }; 37 38 // 音乐玩具类 39 class MusicToy : public Toy { 40 private: 41 int songCount; 42 43 public: 44 MusicToy(const std::string& n, int songs); 45 void specialAbility() override; 46 void play() override; 47 }; 48 49 // 玩具工厂类 50 class ToyFactory { 51 private: 52 std::vector<std::unique_ptr<Toy>> toys; 53 54 public: 55 void addToy(std::unique_ptr<Toy> toy); 56 void showAllToys() const; 57 void testAllAbilities() const; 58 void playWithAll() const; 59 };
Toy.cpp
1 #include "Toy.hpp" 2 3 // Toy基类实现 4 Toy::Toy(const std::string& n, const std::string& t) 5 : name(n), type(t), battery(100) {} 6 7 void Toy::play() { 8 if(battery > 20) { 9 std::cout << name << "正在开心地玩耍!" << std::endl; 10 battery -= 20; 11 } else { 12 std::cout << name << "电量不足,需要充电!" << std::endl; 13 } 14 } 15 16 void Toy::charge() { 17 battery = 100; 18 std::cout << name << "充电完成!" << std::endl; 19 } 20 21 void Toy::showInfo() const { 22 std::cout << "玩具: " << name << " | 类型: " << type 23 << " | 电量: " << battery << "%" << std::endl; 24 } 25 26 std::string Toy::getName() const { return name; } 27 std::string Toy::getType() const { return type; } 28 29 // AnimalToy类实现 30 AnimalToy::AnimalToy(const std::string& n, const std::string& sound) 31 : Toy(n, "动物玩具"), animalSound(sound) {} 32 33 void AnimalToy::specialAbility() { 34 std::cout << name << "发出" << animalSound << "的声音,还会模仿动物走路!" << std::endl; 35 } 36 37 void AnimalToy::play() { 38 Toy::play(); 39 std::cout << name << ":" << animalSound << "!一起来玩吧!" << std::endl; 40 } 41 42 // MusicToy类实现 43 MusicToy::MusicToy(const std::string& n, int songs) 44 : Toy(n, "音乐玩具"), songCount(songs) {} 45 46 void MusicToy::specialAbility() { 47 std::cout << name << "播放" << songCount << "首歌曲,还会跟着节奏跳舞发光!" << std::endl; 48 } 49 50 void MusicToy::play() { 51 Toy::play(); 52 std::cout << name << ":啦啦啦~音乐时间到!" << std::endl; 53 } 54 55 // ToyFactory类实现 56 void ToyFactory::addToy(std::unique_ptr<Toy> toy) { 57 toys.push_back(std::move(toy)); 58 } 59 60 void ToyFactory::showAllToys() const { 61 std::cout << "\n=== 工厂所有玩具 ===" << std::endl; 62 for(const auto& toy : toys) { 63 toy->showInfo(); 64 } 65 } 66 67 void ToyFactory::testAllAbilities() const { 68 std::cout << "\n=== 测试所有特异功能 ===" << std::endl; 69 for(const auto& toy : toys) { 70 toy->specialAbility(); 71 } 72 } 73 74 void ToyFactory::playWithAll() const { 75 std::cout << "\n=== 与所有玩具互动 ===" << std::endl; 76 for(const auto& toy : toys) { 77 toy->play(); 78 } 79 }
demo4.cpp
1 #include <iostream> 2 #include <memory> 3 #include "Toy.hpp" 4 5 void testToySystem() { 6 ToyFactory factory; 7 8 std::cout << "=== 电子毛绒玩具系统演示 ===" << std::endl; 9 10 // 创建各种玩具 - 使用std::unique_ptr构造函数 11 factory.addToy(std::unique_ptr<AnimalToy>(new AnimalToy("泰迪熊", "咕噜咕噜"))); 12 factory.addToy(std::unique_ptr<MusicToy>(new MusicToy("音乐盒", 8))); 13 factory.addToy(std::unique_ptr<AnimalToy>(new AnimalToy("跳跳虎", "嗷呜~"))); 14 factory.addToy(std::unique_ptr<MusicToy>(new MusicToy("小钢琴", 12))); 15 factory.addToy(std::unique_ptr<AnimalToy>(new AnimalToy("皮皮狗", "汪汪!"))); 16 17 // 显示所有玩具 18 factory.showAllToys(); 19 20 // 测试特异功能 21 factory.testAllAbilities(); 22 23 // 与玩具互动 24 factory.playWithAll(); 25 26 std::cout << "\n=== 演示结束 ===" << std::endl; 27 } 28 29 int main() { 30 testToySystem(); 31 return 0; 32 }
运行结果

总结
1.理解了面向对象三大特性:
继承:代码复用,建立层次关系
组合:对象包含,实现复杂功能
多态:同一接口,不同行为
2.原始指针容易内存泄漏;
虚析构函数防止内存泄漏;
组合优于继承, 除非是严格的“是”关系,否则优先用组合

浙公网安备 33010602011771号