实验4

试验任务1:

GradeCalc.hpp:

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

运行结果:

屏幕截图 2025-11-30 152321

问题回答:

1.std::string course_name; 被组合对象:记录本课程的名字;

   std::vector<int> grades; 被组合对象:保存所有学生成绩;

   std::array<int,5> counts; 被组合对象:保存5个分数段的人数;

   std::array<double,5> rates; 被组合对象:保存5个分数段的人数占比。

2.不合法。push_back 不是 GradeCalc 类的公有接口,它是 std::vector 的成员函数,而 grades 是 GradeCalc 的 private 成员,在类外部无法直接访问 grades ,这违反了封装原则。

              (input也写成了inupt)。

3.(1)连续打印3次统计信息, compute 只会被调用1次;

            标记 is_dirty可以防止重复compute,保证只有发现数据已变时重算。

   (2)不需要。但是要在update_grade(index, new_grade) 中增加is_dirty = true,之后info()会调用compete,进行相关操作。

4.   

 1 void GradeCalc::compute()
 2 {
 3     if (grades.empty())
 4         return;
 5 
 6     counts.fill(0);
 7     rates.fill(0.0);
 8 
 9     // 统计各分数段人数
10     for (auto grade : grades)
11     {
12         if (grade < 60)
13             ++counts[0]; // [0, 60)
14         else if (grade < 70)
15             ++counts[1]; // [60, 70)
16         else if (grade < 80)
17             ++counts[2]; // [70, 80)
18         else if (grade < 90)
19             ++counts[3]; // [80, 90)
20         else
21             ++counts[4]; // [90, 100]
22     }
23 
24     // 统计各分数段比例
25     for (size_t i = 0; i < rates.size(); ++i)
26         rates[i] = counts[i] * 1.0 / grades.size();
27     
28     
29     double mid;
30     size_t n = grades.size();
31     if (n % 2 == 0)
32         mid = (grades[n / 2 - 1] + grades[n / 2]) / 2;
33     else
34         mid = grades[n / 2];
35     std::cout << "中位数:\t" << mid << std::endl;
36 
37     is_dirty = false; // 更新脏标记
38 }

没有先排序后再求中位数,是因为sort在compete前执行了,如果没有先sort,可以先拷贝一个副本,排序后求中位数。

5.不能。当去掉这两行后,如果后续数据发生修改,会在原有基础上进行累加,无法正确进行初始化。

6.(1)无影响。程序仍然能正确运行。

   (2)有影响。去掉grades.reserve(n)后,会触发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 {
 9 public:
10     GradeCalc(const std::string &cname);
11     void input(int n);                 // 录入n个成绩
12     void output() const;               // 输出成绩
13     void sort(bool ascending = false); // 排序 (默认降序)
14     int min() const;                   // 返回最低分
15     int max() const;                   // 返回最高分
16     double average() const;            // 返回平均分
17     void info();                       // 输出成绩统计信息
18 
19 private:
20     void compute(); // 计算成绩统计信息
21 
22 private:
23     std::string course_name;     // 课程名
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 #include "GradeCalc.hpp"
 10 
 11 GradeCalc::GradeCalc(const std::string &cname) : course_name{cname}, is_dirty{true}
 12 {
 13     counts.fill(0);
 14     rates.fill(0);
 15 }
 16 
 17 void GradeCalc::input(int n)
 18 {
 19     if (n < 0)
 20     {
 21         std::cerr << "无效输入! 人数不能为负数\n";
 22         return;
 23     }
 24 
 25     this->reserve(n);
 26 
 27     int grade;
 28 
 29     for (int i = 0; i < n;)
 30     {
 31         std::cin >> grade;
 32         if (grade < 0 || grade > 100)
 33         {
 34             std::cerr << "无效输入! 分数须在[0,100]\n";
 35             continue;
 36         }
 37 
 38         this->push_back(grade);
 39         ++i;
 40     }
 41 
 42     is_dirty = true;
 43 }
 44 
 45 void GradeCalc::output() const
 46 {
 47     for (auto grade : *this)
 48         std::cout << grade << ' ';
 49     std::cout << std::endl;
 50 }
 51 
 52 void GradeCalc::sort(bool ascending)
 53 {
 54     if (ascending)
 55         std::sort(this->begin(), this->end());
 56     else
 57         std::sort(this->begin(), this->end(), std::greater<int>());
 58 }
 59 
 60 int GradeCalc::min() const
 61 {
 62     if (this->empty())
 63         return -1;
 64 
 65     return *std::min_element(this->begin(), this->end());
 66 }
 67 
 68 int GradeCalc::max() const
 69 {
 70     if (this->empty())
 71         return -1;
 72 
 73     return *std::max_element(this->begin(), this->end());
 74 }
 75 
 76 double GradeCalc::average() const
 77 {
 78     if (this->empty())
 79         return 0.0;
 80 
 81     double avg = std::accumulate(this->begin(), this->end(), 0.0) / this->size();
 82     return avg;
 83 }
 84 
 85 void GradeCalc::info()
 86 {
 87     if (is_dirty)
 88         compute();
 89 
 90     std::cout << "课程名称:\t" << course_name << std::endl;
 91     std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() << std::endl;
 92     std::cout << "最高分:\t" << max() << std::endl;
 93     std::cout << "最低分:\t" << min() << std::endl;
 94 
 95     const std::array<std::string, 5> grade_range{"[0, 60) ",
 96                                                  "[60, 70)",
 97                                                  "[70, 80)",
 98                                                  "[80, 90)",
 99                                                  "[90, 100]"};
100 
101     for (int i = static_cast<int>(grade_range.size()) - 1; i >= 0; --i)
102         std::cout << grade_range[i] << "\t: " << counts[i] << "人\t"
103                   << std::fixed << std::setprecision(2) << rates[i] * 100 << "%\n";
104 }
105 
106 void GradeCalc::compute()
107 {
108     if (this->empty())
109         return;
110 
111     counts.fill(0);
112     rates.fill(0);
113 
114     // 统计各分数段人数
115     for (int grade : *this)
116     {
117         if (grade < 60)
118             ++counts[0]; // [0, 60)
119         else if (grade < 70)
120             ++counts[1]; // [60, 70)
121         else if (grade < 80)
122             ++counts[2]; // [70, 80)
123         else if (grade < 90)
124             ++counts[3]; // [80, 90)
125         else
126             ++counts[4]; // [90, 100]
127     }
128 
129     // 统计各分数段比例
130     for (size_t i = 0; i < rates.size(); ++i)
131         rates[i] = counts[i] * 1.0 / this->size();
132 
133     is_dirty = false;
134 }
demo2.cpp:
 1 #include <iostream>
 2 #include <string>
 3 #include "GradeCalc.hpp"
 4 #include <windows.h>
 5 
 6 void test()
 7 {
 8     SetConsoleOutputCP(CP_UTF8);
 9     GradeCalc c1("OOP");
10 
11     std::cout << "录入成绩:\n";
12     c1.input(5);
13 
14     std::cout << "输出成绩:\n";
15     c1.output();
16 
17     std::cout << "排序后成绩:\n";
18     c1.sort();
19     c1.output();
20 
21     std::cout << "*************成绩统计信息*************\n";
22     c1.info();
23 }
24 
25 int main()
26 {
27     test();
28 }

运行结果:

屏幕截图 2025-12-01 190115

问题回答:

1.class GradeCalc : private std::vector<int>。

2.当前继承方式下,基类 vector<int> 的接口不会自动成为 GradeCalc 的接口,因为是私有继承。

   不能编译通过。因为私有继承使基类的所有公有接口在派生类中都变为私有,外部无法直接调用(input也写成inupt了)。

3.for(auto grade: grades)是通过成员变量grades来访问;for(int grade: *this)是通过this指针来访问。

   差异:组合方式封装性更强,外部只能用公有接口访问;继承方式封装性较差,但可以直接用基类接口。

4.组合方案更适合。vector只是用来存储数据,应该是组合而不是用于继承;组合方案耦合度低,封装性更好。

 

试验任务3:

Graph.hpp:
 1 #pragma once
 2 
 3 #include <string>
 4 #include <vector>
 5 
 6 enum class GraphType
 7 {
 8     circle,
 9     triangle,
10     rectangle
11 };
12 
13 // Graph类定义
14 class Graph
15 {
16 public:
17     virtual void draw() {}
18     virtual ~Graph() = default;
19 };
20 
21 // Circle类声明
22 class Circle : public Graph
23 {
24 public:
25     void draw();
26 };
27 
28 // Triangle类声明
29 class Triangle : public Graph
30 {
31 public:
32     void draw();
33 };
34 
35 // Rectangle类声明
36 class Rectangle : public Graph
37 {
38 public:
39     void draw();
40 };
41 
42 // Canvas类声明
43 class Canvas
44 {
45 public:
46     void add(const std::string &type); // 根据字符串添加图形
47     void paint() const;                // 使用统一接口绘制所有图形
48     ~Canvas();                         // 手动释放资源
49 
50 private:
51     std::vector<Graph *> graphs;
52 };
53 
54 // 4. 工具函数
55 GraphType str_to_GraphType(const std::string &s); // 字符串转枚举类型
56 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 {
20     Graph *g = make_graph(type);
21     if (g)
22         graphs.push_back(g);
23 }
24 
25 void Canvas::paint() const
26 {
27     for (Graph *g : graphs)
28         g->draw();
29 }
30 
31 Canvas::~Canvas()
32 {
33     for (Graph *g : graphs)
34         delete g;
35 }
36 
37 // 工具函数实现
38 // 字符串 → 枚举转换
39 GraphType str_to_GraphType(const std::string &s)
40 {
41     std::string t = s;
42     std::transform(s.begin(), s.end(), t.begin(),
43                    [](unsigned char c)
44                    { return std::tolower(c); });
45 
46     if (t == "circle")
47         return GraphType::circle;
48 
49     if (t == "triangle")
50         return GraphType::triangle;
51 
52     if (t == "rectangle")
53         return GraphType::rectangle;
54 
55     return GraphType::circle; // 缺省返回
56 }
57 
58 // 创建图形,返回堆对象指针
59 Graph *make_graph(const std::string &type)
60 {
61     switch (str_to_GraphType(type))
62     {
63     case GraphType::circle:
64         return new Circle;
65     case GraphType::triangle:
66         return new Triangle;
67     case GraphType::rectangle:
68         return new Rectangle;
69     default:
70         return nullptr;
71     }
72 }
demo3.cpp:
 1 #include <string>
 2 #include "Graph.hpp"
 3 #include <windows.h>
 4 
 5 void test()
 6 {
 7     SetConsoleOutputCP(CP_UTF8);
 8     Canvas canvas;
 9 
10     canvas.add("circle");
11     canvas.add("triangle");
12     canvas.add("rectangle");
13     canvas.paint();
14 }
15 
16 int main()
17 {
18     test();
19 }

运行结果:

屏幕截图 2025-12-01 202459

问题回答:

1.(1)std::vector<Graph *> graphs;用于存储所有图形对象的指针集合。

   (2)class Circle : public Graph;class Triangle : public Graph;class Rectangle : public Graph。

2.(1)会始终调用基类Graph的draw函数,而不是重写后的。

     (2)   会发生对象切片,只保留基类的的数据,而丢失派生类的数据信息,导致多态失效。

     (3)  只会调用基类的析构函数,不会调用派生类的析构函数,会造成内存泄漏。

3.  Graph.hpp中:enum class GraphType中要加Star;
                             Star类的声明class Star : public Graph。
     Graph.cpp中:Star类实现void Star::draw();
                             GraphType str_to_GraphType(const std::string &s)要增加if (t == "Star")return GraphType::Star;
                             Graph *make_graph(const std::string &type)要增加case GraphType::Star:return new Star。
     demo3.cpp中:要增加canvas.add("Star")。
4.(1)在 Canvas 类的析构函数中被释放。
   (2)原始指针管理内存:利:简单直接,占用资源少。
                                             弊:需要手动管理生命周期,防止内存泄漏等问题。
     

试验任务4:

Toy.hpp:

 1 #pragma once
 2 
 3 #include <string>
 4 #include <vector>
 5 
 6 enum class ToyType
 7 {
 8     Dog,
 9     Cat,
10     Fox,
11     Rabbit
12 };
13 
14 class Toy
15 {
16 public:
17     Toy(const std::string &name, ToyType type, const std::string &color);
18     virtual ~Toy() = default;
19     virtual void act() const = 0; 
20     std::string getname() const;
21     std::string getcolor() const;
22 
23 private:
24     std::string name;
25     std::string color;
26     ToyType type;
27 };
28 
29 class Dog : public Toy
30 {
31 public:
32     Dog(const std::string &name, const std::string &color) : Toy{name, ToyType::Dog, color} {};
33     void act() const override;
34 };
35 
36 class Cat : public Toy
37 {   
38 public:
39     Cat(const std::string &name, const std::string &color) : Toy{name, ToyType::Cat, color} {};
40     void act() const override;
41 };
42 
43 class Fox : public Toy
44 {
45 public:
46     Fox(const std::string &name, const std::string &color) : Toy{name, ToyType::Fox, color} {};
47     void act() const override;
48 };
49 
50 class Rabbit : public Toy
51 {
52 public:
53     Rabbit(const std::string &name, const std::string &color) : Toy{name, ToyType::Rabbit, color} {};
54     void act() const override;
55 };
56 
57 class ToyFactory
58 {
59     private:
60     std::vector<Toy*> toys;
61     public:
62     void add(const std::string &name, ToyType type, const std::string &color);
63     void display() const;
64     ~ToyFactory();
65 };
66 
67 Toy* creat(const std::string &name, ToyType type, const std::string &color);

Toy.cpp:

 1 #include "Toy.hpp"
 2 #include <iostream>
 3 #include <string>
 4 
 5 Toy::Toy(const std::string &name, ToyType type,const std::string &color) : name{name}, type{type}, color{color}{}
 6 
 7 std::string Toy::getname() const
 8 {
 9     return name;
10 }
11 
12 std::string Toy::getcolor() const
13 {
14     return color;
15 }
16 
17 void Dog::act() const
18 {
19     std::cout << "[" << "Dog " << getcolor() << "]" << getname() << " is protecting his owner!" << std::endl;
20 }
21 
22 void Cat::act() const
23 {
24     std::cout << "[" << "Cat " << getcolor() << "]" << getname() << " is sleeping!" << std::endl;
25 }
26 
27 void Fox::act() const
28 {
29     std::cout << "[" << "Fox " << getcolor() << "]" << getname() << " says that It's called a hustle, sweetheart." << std::endl;
30 }
31 
32 void Rabbit::act() const
33 {
34     std::cout << "[" << "Rabbit " << getcolor() << "]" << getname() << " says that I am not a dumb bunny." << std::endl;
35 }
36 
37 void ToyFactory::display() const
38 {
39     for (const auto& toy : toys)
40     {
41         toy->act();
42     }
43 }
44 
45 void ToyFactory::add(const std::string &name, ToyType type, const std::string &color)
46 {
47     Toy* new_toy = creat(name, type, color);
48     if (new_toy)
49     {
50         toys.push_back(new_toy);
51     }
52 }
53 
54 Toy *creat(const std::string &name, ToyType type, const std::string &color)
55 {
56     switch (type)
57     {
58     case ToyType::Dog:
59         return new Dog(name, color);
60     case ToyType::Cat:
61         return new Cat(name, color);
62     case ToyType::Fox:
63         return new Fox(name, color);
64     case ToyType::Rabbit:
65         return new Rabbit(name, color);
66     default:
67         return nullptr;
68     }
69 }
70 
71 ToyFactory::~ToyFactory()
72 {
73     for (Toy* toy : toys)
74     {
75         delete toy;
76     }
77 }

demo4.cpp:

 1 #include <string>
 2 #include <iostream>
 3 #include "Toy.hpp"
 4 
 5 void test()
 6 {
 7     ToyFactory factory;
 8     factory.add("Bruce", ToyType::Dog, "Brown");
 9     factory.add("Garfield", ToyType::Cat, "orange");
10     factory.add("Nick Wilde", ToyType::Fox, "orange");
11     factory.add("Judy Hopps", ToyType::Rabbit, "Gray");
12 
13     factory.display();
14 }
15 
16 int main()
17 {
18     test();
19 }

运行结果:

屏幕截图 2025-12-01 222001

说明:

问题描述:

        设计一款电子毛绒玩具工厂管理系统,支持多种动物造型电子毛绒玩具(狗、猫、狐狸、兔子)的创建、添加与特定功能,系统需通过统一接口调用所有玩具的特异功能。而这里实现的是种类、颜色、名称、动作的显示。

对象关系:

继承关系:Toy封装了名称、种类、颜色和核心接口act,dog、cat、fox、rabbit均继承自Toy,是Toy的派生,并且对act重写;

组合关系:ToyFactory中包含std::vector<Toy*> toys;

多态关系:通过act指针,动态绑定到act的实现,进而实现了一个接口调用所有特异功能。

 

posted @ 2025-12-01 22:35  noeleven  阅读(1)  评论(0)    收藏  举报