实验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 };
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

运行结果:

屏幕截图 2025-11-26 160051

问题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 };
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

运行结果:

屏幕截图 2025-11-26 195240

问题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);  // 创建图形,返回堆对象指针
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

运行结果:

屏幕截图 2025-11-26 201252

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

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

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

运行结果

屏幕截图 2025-11-26 211644

总结

1.理解了面向对象三大特性:

继承:代码复用,建立层次关系

组合:对象包含,实现复杂功能

多态:同一接口,不同行为

2.原始指针容易内存泄漏;

虚析构函数防止内存泄漏;

组合优于继承, 除非是严格的“是”关系,否则优先用组合

posted @ 2025-11-26 21:25  南方之木  阅读(1)  评论(0)    收藏  举报