实验4

实验任务1:

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 };
View Code

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 }
View Code

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 }
View Code

运行测试结果:

image

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

std::string course_name; // 课程名

组合了一个字符串对象,用来记录课程名。


std::vector<int> grades; // 课程成绩

组合了一个int类型的vector对象用来记录这个课程的成绩。


std::array<int, 5> counts; // 保存各分数段人数([0, 60), [60, 70), [70, 80), [80, 90), [90, 100]

组合了一个int类型的长度为5的一维数组,用来记录各分段的人数。


std::array<double, 5> rates; // 保存各分数段人数占比

组合了一个double类型的长度为5的一维数组,用来记录各分段的人数占比。

 

问题2:接口暴露理解
如在 test 模块中这样使用,是否合法?如不合法,解释原因。
答:不合法,push_back不是GradeCalc的成员函数,他是vector的成员函数,但是GradeCalc是在类中组合了vector,并没有继承vector的成员函数,所以无法调用push_back。
 
问题3:架构设计分析
当前设计方案中, compute 在 info 模块中调用:
(1)连续打印3次统计信息, compute 会被调用几次?标记 is_dirty 起到什么作用?
答:只会调用1次,在初始化类之后,is-dirty被标记为true,所以在第一次调用info时会调用一次compute,然后is_dirty被标记为false,在后续调用info中就不会继续调用compute。
(2)如新增 update_grade(index, new_grade) ,这种设计需要更改 compute 调用位置吗?简洁说明理由。
答:不需要,compute是用来统计各分数段人数和占比的函数,如果新增update_grade函数用来更新某个成绩,在函数设计中如果能将is_dirty更改为true,那么在统计信息的时候就会重新调用compute来重新统计。
 
问题4:功能扩展设计
要增加"中位数"统计,不新增数据成员怎么做?在哪个函数里加?写出伪代码。
答:
可以在info这个成员函数中统计出来,因为如果要统计中位数就需要对成绩进行排序,info的调用是在sort之后,正好省去了额外调用sort的步骤。
伪代码如下:
int size = grades.size();
if(size % 2 == 0)
{
  std::cout << "中位数为:"<< (grades[size/2-1]+grades[size/2])/2 << endl;
}
else
{
  std::cout << "中位数为:" << grades[size/2] << endl;
}
 
问题5:数据状态管理
GradeCalc 和 compute 中都包含代码: counts.fill(0); rates.fill(0); 。
compute 中能否去掉这两行?如去掉,在哪种使用场景下会引发统计错误?
答:不能,如果去掉,那么各分段人数和占比只在第一次统计时正确统计,如果后续对数据进行更改,由于counts和rates没有初始化,导致统计是在之前的状态上叠加,导致最后的统计结果错误。
 
问题6:内存管理理解
input 模块中代码 grades.reserve(n); 如果去掉:
(1)对程序功能有影响吗?(去掉重新编译、运行,观察功能是否受影响)
答:没有。
(2)对性能有影响吗?如有影响,用一句话陈述具体影响。
答:有,reserve用于预分配内存,如果不使用reserve会导致后续如果vector容量不足需要扩容导致性能下降。
 
实验任务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 };
View Code

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 }
View Code

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 }
View Code
运行测试结果:
image
问题1:继承关系识别
写出 GradeCalc 类声明体现"继承"关系的完整代码行。
答:class GradeCalc : private std::vector<int>
GradeCalc私有继承了int类型的vector类
 
问题2:接口暴露理解
当前继承方式下,基类 vector<int> 的接口会自动成为 GradeCalc 的接口吗?
如在 test 模块中这样用,能否编译通过?用一句话解释原因。
答:不会,编译不通过,私有继承下基类的public成员在派生类中成为private成员,在外部不能调用。
 
问题3:数据访问差异
对比继承方式与组合方式内部实现数据访问的一行典型代码。说明两种方式下的封装差异带来的数据访问接口差异。
答:
for(auto grade: grades) 通过私有成员grades来访问数据。
for(int grade: *this)通过派生类对象本身来访问数据。
 
问题4:组合 vs. 继承方案选择
你认为组合方案和继承方案,哪个更适合成绩计算这个问题场景?简洁陈述你的结论和理由。
答:我认为组合的方案更适合这个场景,继承的话是让这个类拥有了vector的特质,而这个类设计的是一个成绩统计器,从问题本身来说不应该具有vector的性质,成绩更应该是它的成员,继承会导致派生类与基类之间的耦合度变高,如果对基类进行修改,可能导致派生类需要大幅度修改。
 
实验任务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);  // 创建图形,返回堆对象指针
View Code

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 }
View Code

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 }
View Code

运行测试结果:

image

问题1:对象关系识别
(1)写出Graph.hpp中体现"组合"关系的成员声明代码行,并用一句话说明被组合对象的功能。
答:std::vector<Graph*> graphs;
组合了一个用来存储多个图形对象的指针的vector成员。
(2)写出Graph.hpp中体现"继承"关系的类声明代码行。
答:
class Circle : public Graph
class Triangle : public Graph
class Rectangle : public Graph
 
问题2:多态机制观察
(1) Graph 中的 draw 若未声明成虚函数, Canvas::paint() 中 g->draw() 运行结果会有何不同?
答:未声明成虚函数最后什么都没有输出,因为基类Gragh的draw实现是没有任何输出,如果没有将其声明为虚函数,在定义指针时是Gragh类型,就算赋值为其他派生类,后续调用时也会使用基类的同名函数导致没有输出。
(2)若 Canvas 类 std::vector<Graph*> 改成 std::vector<Graph> ,会出现什么问题?
答:改成Graph就不是指针,存入对象时只会保存Graph的特性也就是把这些派生类全变成基类了,无法实现多态。
(3)若 ~Graph() 未声明成虚函数,会带来什么问题?
答:会导致派生类在调用析构函数时不使用自己的而使用基类的析构函数,导致资源释放不完全,只能释放派生类中和包含基类的资源不能释放除此之外的资源。
 
问题3:扩展性思考
若要新增星形 Star ,需在哪些文件做哪些改动?逐一列出。
答:在 Graph.hpp加上Star的声明和定义,在枚举中加上Star类;
在 Graph.cpp中加上类的实现,同时加上str_to_GraphType和make_graph中Star的部分;
在 demo3.cpp中加上canvas.add(“Star”);
 
问题4:资源管理
观察 make_graph 函数和 Canvas 析构函数:
(1) make_graph 返回的对象在什么地方被释放?
答:在Canvas的析构函数中释放,因为返回的是new+类名,所以返回的不是一个类对象而是这个对象的内存地址,也就是一个堆对象指针,而这个堆对象指针是由Canvas创建的,那么也由Canva的析构函数释放。
(2)使用原始指针管理内存有何利弊?
答:
利:可以按照程序员的喜好自行创建或销毁。
弊:如果忘记释放会一直占用内存,对象被删除后指针如果没被置空就会变成野指针。
 
 实验任务4:
 Toy.hpp源代码:
 1 #pragma once
 2 
 3 #include<string>
 4 
 5 #include<vector>
 6 
 7 enum class ToyType {DancingToy, SingingToy, ReadingToy};
 8 
 9 //基类Toy定义
10 class Toy {
11 public:
12     //构造函数
13     Toy(const std::string &name , const ToyType &type):name{name},type{type}{}
14     //析构函数
15     virtual ~Toy()=default;
16     //公开接口展示信息
17     void info();
18     //特异功能
19     virtual void specialability(){}
20 
21 private:
22     const std::string name;
23     const ToyType type;
24 };
25 
26 //舞蹈玩具定义
27 class DancingToy :public Toy {
28 public:
29     DancingToy(const ToyType type , const std::string& name = "bear") : Toy(name, type)
30     {
31         dance_music = { "Gee", "不潮不用花钱", "明月几时有" };
32     }
33     void specialability();
34 private:
35     std::vector<std::string> dance_music;
36 };
37 
38 //唱歌玩具定义
39 class SingingToy :public Toy {
40 public:
41     SingingToy(const ToyType type , const std::string& name = "dolphin") : Toy(name, type)
42     {
43         songs = { "Love story", "Remember us this way", "Love me harder" };
44     }
45     void specialability();
46 private:
47     std::vector<std::string> songs;
48 };
49 
50 //读书玩具定义
51 class ReadingToy :public Toy {
52 public:
53     ReadingToy(const ToyType type , const std::string& name = "monkey") : Toy(name, type)
54     {
55         books = { "小王子","白雪公主","勇者斗恶龙" };
56     }
57     void specialability();
58 private:
59     std::vector<std::string> books;
60 };
61 
62 //玩具工厂定义
63 class ToyFactory {
64 public:
65     void add(const std::string& type);    //根据字符串添加玩具
66     void show() const;    //展示所有玩具的信息
67     void ableshow() const;    //展示所有玩具的特殊功能
68     ~ToyFactory();
69 
70 private:
71     std::vector<Toy*> toys;
72 };
73 
74 ToyType str_to_ToyType(const std::string& s);        //字符串转枚举类型
75 Toy* make_toy(const std::string& type);        //创建玩具时,返回堆对象指针
View Code

Toy.cpp源代码:

  1 #include <iostream>
  2 #include <algorithm>
  3 #include <cctype>
  4 #include <string>
  5 
  6 #include"Toy.hpp"
  7 
  8 // 工具函数实现
  9 // 字符串 → 枚举转换
 10 ToyType str_to_ToyType(const std::string& s) {
 11     std::string t = s;
 12 
 13     //转换成小写
 14     std::transform(s.begin(), s.end(), t.begin(),
 15         [](unsigned char c) { return std::tolower(c); });
 16 
 17     if (t == "dancingtoy")
 18         return ToyType::DancingToy;
 19 
 20     if (t == "singingtoy")
 21         return ToyType::SingingToy;
 22 
 23     if (t == "readingtoy")
 24         return ToyType::ReadingToy;
 25 
 26     return ToyType::DancingToy; // 缺省返回
 27 }
 28 
 29 // 创建图形,返回堆对象指针
 30 Toy* make_toy(const std::string& type) {
 31 
 32     ToyType t = str_to_ToyType(type);
 33 
 34     switch (t) {
 35     case ToyType::DancingToy: return new DancingToy(t);
 36     case ToyType::SingingToy: return new SingingToy(t);
 37     case ToyType::ReadingToy: return new ReadingToy(t);
 38     default: return nullptr;
 39     }
 40 }
 41 
 42 //添加玩具
 43 void ToyFactory::add(const std::string& type)
 44 {
 45     Toy* t = make_toy(type);
 46 
 47     if (t)
 48     {
 49         toys.push_back(t);
 50     }
 51 }
 52 
 53 //展示玩具信息
 54 void Toy::info()
 55 {
 56     std::cout << name << std::endl;
 57 }
 58 
 59 //特异功能
 60 void DancingToy::specialability()
 61 {
 62     std::cout << "bear会跳";
 63     for (auto i : dance_music)
 64     {
 65         std::cout << i << " ";
 66     }
 67     std::cout << std::endl;
 68 }
 69 
 70 void SingingToy::specialability()
 71 {
 72     std::cout << "dolphin会唱";
 73     for (auto i : songs)
 74     {
 75         std::cout << i << " ";
 76     }
 77     std::cout << std::endl;
 78 }
 79 
 80 void ReadingToy::specialability()
 81 {
 82     std::cout << "monkey会读";
 83     for (auto i : books)
 84     {
 85         std::cout << i << " ";
 86     }
 87     std::cout << std::endl;
 88 }
 89 
 90 //展示工厂玩具
 91 void ToyFactory::show() const
 92 {
 93     for (auto i : toys)
 94     {
 95         i->info();
 96     }
 97 }
 98 
 99 void ToyFactory::ableshow() const
100 {
101     for (auto i : toys)
102     {
103         i->specialability();
104     }
105 }
106 
107 ToyFactory::~ToyFactory()
108 {
109     for (auto i : toys)
110     {
111         delete i;
112     }
113 }
View Code

demo4.cpp源代码:

 1 #include "Toy.hpp"
 2 #include <string>
 3 #include <iostream>
 4 
 5 void test()
 6 {
 7     ToyFactory t;
 8 
 9     std::cout << "共有以下玩具" << std::endl;
10     t.add("DancingToy");
11     t.add("SingingToy");
12     t.add("ReadingToy");
13 
14     t.show();
15     t.ableshow();
16 }
17 
18 int main()
19 {
20     test();
21     
22     return 0;
23 }
View Code
运行测试结果:
image

设计思路:

我的设计思路大致与实验任务3类似,我将玩具名称和类型作为基类的private成员,因此派生类无法直接使用,所以我添加了一个公开的info接口可以调用私有成员,关于特异功能的设计,我使用了虚函数同时在每个玩具内部添加了私有成员vector来存储他们能表演的歌曲/舞蹈/书,通过虚函数的设计和堆对象指针的设计,只需要通过简短的代码就可以调用所有玩具的功能。关于类的设计,我既使用了组合也使用了继承,每个特别的玩具类都继承自基类Toy,每个派生类内部都组合了一个私有成员vector用于存储能力。

 

实验总结:本次实验前三个都是验证性实验,因此代码方面比较顺利,通过实验,我理解了继承和组合的区别,堆对象指针,多态,虚函数等知识。但是到实验任务4需要我自己实现一个项目时,编码方面就会有点困难,一开始我不理解为什么需要写const限定,但是当我编码到const std::string& name = "bear",也就是我想给这个玩具类的名字赋初值时,如果没有const限定就开始报错,这也是我第一次使用堆对象指针,使用之后才能感受到比较方便。代码设计和实验任务3差距不是特别大,所以更多的是让我熟悉这种代码组织方式。

posted @ 2025-11-27 09:22  王宝炜  阅读(0)  评论(0)    收藏  举报