实验4 组合与继承

实验四:

实验任务1

源代码:

 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.hpp
  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 }
GradeCalc.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 }
task1.cpp

运行结果:

image

问题回答:

  • 问题1:组合关系识别

   GradeCalc 类声明中,逐行写出所有体现"组合"关系的成员声明,并用一句话说明每个被组合对象的功能。

  std::string course_name;              存储课程名称
  std::vector<int> grades;      存储成绩
  std::array<int, 5> counts;              存储各个分数段
  std::array<double, 5> rates;          存储各分数段人数占比

  • 问题2:接口暴露理解

  如在 test 模块中这样使用,是否合法?如不合法,解释原因。

1 GradeCalc c("OOP");
2 c.inupt(5);
3 c.push_back(97); // 合法吗?

  不合法:一是input拼错了,二是grades是GradeCalc的私有成员,push_back是std::vector的成员函数,外部不能直接访问私有成员,并且GradeCalc没有暴露这个接口。

  • 问题3:架构设计分析
  当前设计方案中, compute 在 info 模块中调用:
  (1)连续打印3次统计信息, compute 会被调用几次?标记 is_dirty 起到什么作用?
  被调用1次,作用:标记成绩是否发生变更。
     (2)如新增 update_grade(index, new_grade) ,这种设计需要更改 compute 调用位置吗?简洁说明理由。
  不需要:当新增update_grade(index, new_grade),在该函数中修改grades[index],设置is_dirty = true。再调用info时,会自动检测is_dirty然后触发compute重新计算,因此不用改变compute的调用位置。
  • 问题4:功能扩展设计
  要增加"中位数"统计,不新增数据成员怎么做?在哪个函数里加?写出伪代码。
  利用现有的grades,排序计算中位数:增加median()成员函数,在info()中调用并输出。
 1 double median() const;
 2 
 3 double GradeCalc::median() const {
 4     if (grades.empty()) return 0.0; 
 5     std::vector<int> temp_grades = grades; 
 6     std::sort(temp_grades.begin(), temp_grades.end()); 
 7     int n = temp_grades.size();
 8     if (n % 2 == 1) {
 9         return temp_grades[n / 2]; 
10     } else {
11         return (temp_grades[n/2 - 1] + temp_grades[n/2]) / 2.0; 
12     }
13 }
14 
15 cout << "中位数:\t" << std::fixed << std::setprecision(2) << median() << endl;
  • 问题5:数据状态管理

  GradeCalc 和 compute 中都包含代码: counts.fill(0); rates.fill(0); 。
  compute 中能否去掉这两行?如去掉,在哪种使用场景下会引发统计错误?

  不能删去:若变更成绩时,由于此时counts和rates未归0,counts和rates在已有数据上累加,导致统计结果错误。

  • 问题6:内存管理理解
  input 模块中代码 grades.reserve(n); 如果去掉:
  (1)对程序功能有影响吗?(去掉重新编译、运行,观察功能是否受影响)
  无影响

       image

  (2)对性能有影响吗?如有影响,用一句话陈述具体影响。
  有影响:当 n 很大,会增加vector内存重新分配和元素拷贝的次数,降低程序运行效率。
 

实验任务2

源代码:

 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.hpp
  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 }
GradeCalc.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 }
task2.cpp

运行结果:

image

 问题回答:

  • 问题1:继承关系识别

  写出 GradeCalc 类声明体现"继承"关系的完整代码行

1 class GradeCalc: private std::vector<int>
  • 问题2:接口暴露理解

  当前继承方式下,基类 vector<int> 的接口会自动成为 GradeCalc 的接口吗?
  如在 test 模块中这样用,能否编译通过?用一句话解释原因。

1 GradeCalc c("OOP");
2 c.input(5);
3 c.push_back(97); // 合法吗?

  会成为接口;不能编译通过:当前继承方式为私有继承,私有继承隐藏基类std::vector<int>的公有接口,但派生类GradeCalc没有将该接口暴露为自身的公有接口,因此对象c无法直接调用基类的公有成员函数push_back。

  • 问题3:数据访问差异
  对比继承方式与组合方式内部实现数据访问的一行典型代码。说明两种方式下的封装差异带来的数据访问接口差异。
1 // 组合方式
2 for(auto grade: grades) // 通过什么接口访问数据
3 //4 // 继承方式
5 for(int grade: *this) // 通过什么接口访问数据
6 //

   继承方式的封装性弱 :GradeCalc私有继承std::vector<int>,数据存储直接依赖基类。数据访问接口本质是复用基类vector的迭代器接口(begin()和end()),接口由基类决定,派生类被动依赖,基类接口的变化会直接影响内部访问的逻辑。

   组合方式的封装性强:若将std::vector<int>作为GradeCalc的私有成员,数据存储则完全由类内部管控。数据访问接口来自成员对象的迭代器接口,外部无法直接访问存储数据,并且类可自行决定暴露哪些接口,就算后续替换存储容器也会不影响外部使用。

  • 问题4:组合 vs. 继承方案选择
  你认为组合方案和继承方案,哪个更适合成绩计算这个问题场景?简洁陈述你的结论和理由。
  组合方案更适合:成绩计算(GradeCalc)类的核心功能是 “录入成绩、统计成绩、排序成绩”,而不是扩展容器功能。组合(has-a包含一个容器)更符合GradeCalc类需要一个容器存储成绩的逻辑;且组合的封装性更强,可避免与基类的接口发生耦合,而如果后续需更换存储容器,仅需修改内部成员变量,不影响外部接口;而继承(is-a是一个容器)违背了GradeCalc类的本质逻辑,此外私有继承存在基类依赖,使得灵活性和可维护性相对组合更差。
 

实验任务3

源代码:

 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.hpp
 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 }
Graph.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 }
demo3.cpp

实验结果:

image

 问题回答:

  • 问题1:对象关系识别

  (1)写出Graph.hpp中体现"组合"关系的成员声明代码行,并用一句话说明被组合对象的功能。

1 private: std::vector<Graph*> graphs;

  功能:存储所有图形对象的指针。

  (2)写出Graph.hpp中体现"继承"关系的类声明代码行。

1 class Circle : public Graph;
2 class Triangle : public Graph;
3 class Rectangle : public Graph;
  • 问题2:多态机制观察
  (1) Graph 中的 draw 若未声明成虚函数, Canvas::paint() 中 g->draw() 运行结果会有何不同?
  无法绘制图形:g->draw() 会调用基类Graph的空实现draw() 方法,不是派生类(Circle/Triangle/Rectangle)的重写版本,无法实现绘制图形的功能,因为在未声明虚函数时函数调用采用静态绑定,使得多态机制失效。
  (2)若 Canvas 类 std::vector<Graph*> 改成 std::vector<Graph> ,会出现什么问题?
  无法绘制图形:存储派生类对象时,仅保留基类Graph的成员,派生类的特有信息会丢失,并且调用 draw() 时,由于对象本质是基类实例,无法触发派生类的重写方法,使得多态失效,导致无法绘制出对应的图形。
  (3)若 ~Graph() 未声明成虚函数,会带来什么问题?
  导致内存泄漏:Canvas析构时遍历graphs向量,通过基类指针Graph* 删除派生类对象时,仅调用基类Graph的析构函数,派生类的析构函数不会被执行,则会产生内存泄漏的问题。
  • 问题3:扩展性思考
  若要新增星形 Star ,需在哪些文件做哪些改动?逐一列出。
  Graph.hpp:
1 //在枚举GraphType中添加star
2 enum class GraphType {circle, triangle, rectangle, star};
3 
4 //声明Star类
5 class Star : public Graph {
6 public:
7     void draw();
8 };
  Graph.cpp:
1 //实现Star类的draw()方法:
2 void Star::draw() { std::cout << "draw a star...\n"; }
3 
4 //修改str_to_GraphType函数,添加Star对应的字符串转换为枚举:
5 if (t == "star") return GraphType::star;
6 
7 //修改make_graph函数的switch语句,添加Star的创建:
8 case GraphType::star: return new Star;
  demo3.cpp:
1 //在test( 函数中添加Star的调用:
2 canvas.add("star");
  • 问题4:资源管理
  观察 make_graph 函数和 Canvas 析构函数:
  (1) make_graph 返回的对象在什么地方被释放?
   Canvas类的析构函数中.
  (2)使用原始指针管理内存有何利弊? 

  利: 支持多态:如vector<Graph*> 存派生类指针,实现 draw() 多态调用

        操作无额外开销:直接存、取、释放指针,没有额外的内存开销

 弊:容易发生内存泄漏:完全依赖Canvas析构函数的for (Graph* g : graphs) delete g; 释放,若代码被修改则可能导致内存泄漏。

     有产生野指针风险:指针内存释放后未置空,会产生野指针。

 

实验任务4

 源代码:

    toy.hpp

 1 #ifndef TOY_HPP
 2 #define TOY_HPP
 3 
 4 #include <string>
 5 #include <vector>
 6 using namespace std;
 7 
 8 class Toy {
 9 protected:
10     string name;
11     string type;
12     string color;
13 public:
14     Toy(string n, string t, string c);
15     virtual ~Toy();
16     void showBasicInfo() const;
17     virtual void showSpecialAbility() const = 0;
18 };
19 
20 class TalkingToy : public Toy {
21 private:
22     string phrase;
23 public:
24     TalkingToy(string n, string t, string c, string p);
25     void showSpecialAbility() const override;
26 };
27 
28 class SingingToy : public Toy {
29 private:
30     string song;
31 public:
32     SingingToy(string n, string t, string c, string s);
33     void showSpecialAbility() const override;
34 };
35 
36 class DancingToy : public Toy {
37 private:
38     string danceType;
39 public:
40     DancingToy(string n, string t, string c, string d);
41     void showSpecialAbility() const override;
42 };
43 
44 class ToyFactory {
45 private:
46     vector<Toy*> toyList;
47 public:
48     void addToy(Toy* toy);
49     void showAllToys() const;
50     ~ToyFactory();
51 };
52 
53 #endif // TOY_HPP

toy.cpp

 1 #include "toy.hpp"
 2 #include <iostream>
 3 using namespace std;
 4 
 5 Toy::Toy(string n, string t, string c) : name(n), type(t), color(c) {}
 6 
 7 Toy::~Toy() {}
 8 
 9 void Toy::showBasicInfo() const {
10     cout << "名称:" << name << ",类型:" << type << ",颜色:" << color;
11 }
12 
13 TalkingToy::TalkingToy(string n, string t, string c, string p)
14     : Toy(n, t, c), phrase(p) {
15 }
16 
17 void TalkingToy::showSpecialAbility() const {
18     cout << ",特异功能:会说:“" << phrase << "" << endl;
19 }
20 
21 SingingToy::SingingToy(string n, string t, string c, string s)
22     : Toy(n, t, c), song(s) {
23 }
24 
25 void SingingToy::showSpecialAbility() const {
26     cout << ",特异功能:会唱:《" << song << "" << endl;
27 }
28 
29 DancingToy::DancingToy(string n, string t, string c, string d)
30     : Toy(n, t, c), danceType(d) {
31 }
32 
33 void DancingToy::showSpecialAbility() const {
34     cout << ",特异功能:会跳" << danceType << "" << endl;
35 }
36 
37 void ToyFactory::addToy(Toy* toy) {
38     toyList.push_back(toy);
39 }
40 
41 void ToyFactory::showAllToys() const {
42     cout << "===== 玩具工厂 所有玩具信息 =====" << endl;
43     for (size_t i = 0; i < toyList.size(); ++i) {
44         cout << "【玩具" << i + 1 << "";
45         toyList[i]->showBasicInfo();
46         toyList[i]->showSpecialAbility();
47         cout << "------------------------" << endl;
48     }
49 }
50 
51 ToyFactory::~ToyFactory() {
52     for (Toy* toy : toyList) {
53         delete toy;
54     }
55     toyList.clear();
56 }

demo4.cpp

 1 #include "toy.hpp"
 2 #include <iostream>
 3 using namespace std;
 4 
 5 int main() {
 6     ToyFactory myFactory;
 7 
 8     myFactory.addToy(new TalkingToy("Labubu", "毛绒公仔", "深棕色", "你好你好你好"));
 9     myFactory.addToy(new SingingToy("千早爱音", "互动毛绒玩偶", "纯白色", "嘻嘻嘻嘻嘻"));
10     myFactory.addToy(new DancingToy("叮咚鸡", "动感毛绒玩偶", "黑白色", "叮咚鸡完大狗叫"));
11     myFactory.addToy(new TalkingToy("小猪佩奇", "萌系毛绒公仔", "粉红色", "你们好呀我是小猪佩奇"));
12 
13     myFactory.showAllToys();
14 
15     return 0;
16 }

运行结果:

image

问题场景描述:

模拟毛绒玩具工厂管理场景:工厂生产TalkingToy、SingingToy、DancingToy等毛绒玩具,需通过 ToyFactory 的 toyList 统一存储所有玩具,展示其 name、type、color等基础信息,并调用各玩具的特异功能(phrase 、song 、danceType ),同时支持新增玩具类型。

类间关系及设计理由:

继承关系:TalkingToy/SingingToy/DancingToy继承Toy,复用name/type/color 及 showBasicInfo,通过纯虚函数 showSpecialAbility 统一接口,同时运用了多态支持功能扩展。

组合关系:ToyFactory 组合 vector<Toy*>,使得工厂可以集中管理玩具,兼容玩具的不同类型,统一实现添加、展示、内存释放等功能。

 

实验总结

  本次实验我通过组合实现成绩计算器、继承复用容器功能、多态实现图形绘制及综合设计玩具工厂四大任务,深入理解了组合 “has-a(包含一个容器)” 的封装优势与继承 “is-a(是一个容器)” 的复用特性,明晰了多态依赖虚函数和基类指针实现统一接口的核心逻辑,不仅掌握了面向对象编程的关键语法,更学会了根据场景选型设计可扩展、易维护的类结构,真正的提升了从问题分析到代码实现的实践能力。

 

posted @ 2025-12-02 13:26  景思翰  阅读(12)  评论(0)    收藏  举报