实验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     int n;
 8     std::cout << "请输入您要录入的成绩数目:\n";
 9     std::cin >>n; 
10     
11     std::cout << "录入成绩:\n";
12     c1.input(n);
13 
14     std::cout << "输出成绩:\n";
15     c1.output();
16 
17     std::cout << "排序后成绩:\n";
18     c1.sort(); c1.output();
19 
20     std::cout << "*************成绩统计信息*************\n";
21     c1.info();
22 
23 }
24 
25 int main() {
26     test();
27 }
task1.cpp

 截图:

image

 

Q1:

std::vector<int> grades;

std::array<int, 5> counts;

std::array<double, 5> rates;

grades负责存储所有学生成绩,而counts和rates则分别用于在统计时临时存放五个分数段的人数和占比。

Q2:

不合法。因为push_back是std::vector的成员函数,而grades是GradeCalc类的私有成员变量,外部代码无法直接访问,GradeCalc类自身也并未提供任何名为push_back的公有接口来修改内部成绩列表。

Q3:
(1) compute()只会被调用1次。is_dirty确保仅在数据被修改后才执行统计计算,在数据未变的情况下直接使用缓存结果,提高效率。

(2) 无需更改compute的调用位置;只需在该函数内更新成绩并设置is_dirty = true,之后info()在需要时会自动触发重算。

Q4:

应在info()函数内添加逻辑。

伪代码为:

先调用sort(true)对成绩进行升序排序;若成绩数量为奇数,则中位数为正中间成绩grades[n/2];若为偶数,则中位数为中间两个成绩的平均值(grades[n/2-1] + grades[n/2]) / 2.0。

Q5:

不能去掉。如果去掉,在连续多次调用info()且中间有成绩数据更新的场景下,compute()函数会在旧的统计结果基础上累加新数据,引发统计错误。

Q6:

(1) 对程序功能没有影响。

(2) 对性能有影响:去掉后,vector在动态增长过程中可能需要多次重新分配内存,当成绩数量n较大时,会导致额外的开销。

 

任务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     int n;
 8     std::cout << "请输入您要录入的成绩数目:\n";
 9     std::cin >>n; 
10 
11     std::cout << "录入成绩:\n";
12     c1.input(n);
13 
14     std::cout << "输出成绩:\n";
15     c1.output();
16 
17     std::cout << "排序后成绩:\n";
18     c1.sort(); c1.output();
19 
20     std::cout << "*************成绩统计信息*************\n";
21     c1.info();
22 
23 }
24 
25 int main() {
26     test();
27 }
task2.cpp

截图:

image

 

Q1:GradeCalc类声明中体现"继承"关系的完整代码行:class GradeCalc : private std::vector<int>。

Q2:基类vector<int>的接口不会自动成为GradeCalc的接口。测试代码不能编译通过,因为push_back()方法在私有继承后变成了GradeCalc的私有成员,外部无法直接访问。

Q3:在组合方式中,通过包含对象的公有接口访问数据,而在继承方式中,通过继承关系直接访问基类成员。

Q4:在成绩计算这个场景下,组合方案更为合适。因为成绩GradeCalc与向量vector之间是"has-a"关系而非"is-a"关系,组合方式能提供更好的封装性,避免意外暴露不必要的向量接口。

任务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

 

Q1:

(1)std::vector<Graph*> graphs;管理和存储所有图形对象的指针;

(2)class Circle : public Graph ||| class Triangle : public Graph ||| class Rectangle : public Graph

Q2:

(1)将无法实现多态,只会调用基类 Graph的 draw方法

(2)会导致仅保留基类部分而丢失派生类信息

(3)通过基类指针删除派生类对象时,派生类的析构函数不会被调用,导致资源泄漏。
Q3:需Graph.hpp中增加 star枚举值、声明 Star类,在实现文件中实现 Star::draw()方法,并修改 str_to_GraphType和 make_graph两个函数以支持新类型。

Q4:

(1)make_graph返回的对象在 Canvas的析构函数中被释放;

(2)利在于直接、高效,但弊端是需手动管理释放,极易因疏忽导致内存泄漏或重复释放。

 

 

任务4:

源代码:

 1 #pragma once
 2 
 3 #include <string>
 4 #include <vector>
 5 
 6 // 玩具类型枚举
 7 enum class ToyType { talking_bear, glowing_rabbit, singing_bird };
 8 
 9 // 抽象基类:毛绒玩具
10 class Toy {
11 public:
12     virtual void specialAbility() = 0; // 特异功能
13     virtual void showInfo() const;     // 显示基本信息
14     virtual ~Toy() = default;
15 
16 protected:
17     std::string name;
18     ToyType type;
19     std::string color;
20 };
21 
22 // 会说话的熊
23 class TalkingBear : public Toy {
24 public:
25     TalkingBear();
26     void specialAbility();
27 };
28 
29 // 会发光的兔子
30 class GlowingRabbit : public Toy {
31 public:
32     GlowingRabbit();
33     void specialAbility();
34 };
35 
36 // 会唱歌的小鸟
37 class SingingBird : public Toy {
38 public:
39     SingingBird();
40     void specialAbility();
41 };
42 
43 // 玩具工厂
44 class ToyFactory {
45 public:
46     void addToy(const std::string& type); // 添加玩具
47     void showAllToys() const;             // 显示所有玩具信息
48     ~ToyFactory();                        // 析构函数
49 
50 private:
51     std::vector<Toy*> toys;
52 };
53 
54 // 工具函数
55 ToyType strToToyType(const std::string& s);
56 Toy* makeToy(const std::string& type);
toy.hpp
 1 #include <algorithm>
 2 #include <cctype>
 3 #include <iostream>
 4 #include <string>
 5 #include "toy.hpp"
 6 
 7 // TalkingBear 实现
 8 TalkingBear::TalkingBear() {
 9     name = "说话小熊";
10     type = ToyType::talking_bear;
11     color = "棕色";
12 }
13 
14 void TalkingBear::specialAbility() {
15     std::cout << " 说:你好!今天过得怎么样?\n";
16 }
17 
18 // GlowingRabbit 实现
19 GlowingRabbit::GlowingRabbit() {
20     name = "发光白兔";
21     type = ToyType::glowing_rabbit;
22     color = "白色";
23 }
24 
25 void GlowingRabbit::specialAbility() {
26     std::cout <<" 发出柔和的彩虹光芒!\n";
27 }
28 
29 // SingingBird 实现
30 SingingBird::SingingBird() {
31     name = "唱歌小鸟";
32     type = ToyType::singing_bird;
33     color = "黄色";
34 }
35 
36 void SingingBird::specialAbility() {
37     std::cout <<" 唱起美妙的歌曲!\n";
38 }
39 
40 // Toy 基类实现
41 void Toy::showInfo() const {
42     std::cout << "玩具名称:" << name << std::endl <<"颜色:" << color << std::endl;
43 }
44 
45 // ToyFactory 实现
46 void ToyFactory::addToy(const std::string& type) {
47     Toy* toy = makeToy(type);
48     if (toy) 
49         toys.push_back(toy);
50 }
51 
52 void ToyFactory::showAllToys() const {
53     std::cout << "=== 玩具工厂中的所有玩具 ===\n";
54     for (auto toy : toys) {
55         toy->showInfo();
56         std::cout << "特异功能:";
57         toy->specialAbility();
58         std::cout<<std::endl;
59     }
60 }
61 
62 ToyFactory::~ToyFactory() {
63     for (Toy* toy : toys) 
64         delete toy;
65 }
66 
67 // 工具函数实现
68 ToyType strToToyType(const std::string& s) {
69     std::string t = s;
70     std::transform(s.begin(), s.end(), t.begin(),
71                    [](unsigned char c) { return std::tolower(c); });
72 
73     if (t == "talkingbear") return ToyType::talking_bear;
74     if (t == "glowingrabbit") return ToyType::glowing_rabbit;
75     if (t == "singingbird") return ToyType::singing_bird;
76 
77     return ToyType::talking_bear; // 默认类型
78 }
79 
80 Toy* makeToy(const std::string& type) {
81     switch (strToToyType(type)) {
82     case ToyType::talking_bear:    return new TalkingBear;
83     case ToyType::glowing_rabbit:  return new GlowingRabbit;
84     case ToyType::singing_bird:    return new SingingBird;
85     default: return nullptr;
86     }
87 }
toy.cpp
 1 #include <iostream>
 2 #include <string>
 3 #include "toy.hpp"
 4 
 5 void testToyFactory() {
 6     ToyFactory factory;
 7 
 8     // 添加各种玩具
 9     factory.addToy("talkingbear");
10     factory.addToy("glowingrabbit");
11     factory.addToy("singingbird");
12 
13     // 显示所有玩具信息
14     factory.showAllToys();
15 }
16 
17 int main() {
18     testToyFactory();
19     return 0;
20 }
demo4.cpp

截图:

image

 

  采用了基于继承的多态设计方案,通过抽象基类Toy定义了统一的玩具接口,三个具体玩具类(TalkingBear、GlowingRabbit、SingingBird)继承并实现了各自的特异功能。

  方案采用继承关系实现多态,通过虚函数specialAbility()为不同玩具提供统一的功能调用接口。数据成员包括玩具名称、类型和颜色等基本信息,函数成员设计包含显示信息的showInfo()和展示特异功能的specialAbility()等公共接口。玩具工厂类ToyFactory采用工厂模式管理玩具对象集合,通过addToy()添加玩具、showAllToys()展示所有玩具信息,并利用工具函数strToToyType()和makeToy()实现字符串到枚举的转换及对象创建。

 

实验总结:
本次实验让我深刻体会到面向对象设计中多态的强大威力:通过虚函数实现"一个接口,多种行为",新加玩具类型只需继承基类而无需修改现有代码。

posted @ 2025-11-29 10:37  lei1459  阅读(0)  评论(0)    收藏  举报