实验四

任务一

 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 }
test1.cpp

image

 1.

  std::vector<int> grades; // 存储课程的所有成绩分数。

 std::array<int, 5> counts; // 缓存五个分数段的人数统计。

 std::array<double, 5> rates; // 缓存五个分数段的人数占比。

 std::string course_name; // 存储课程名称。

2.

不合法。因为 push_backstd::vector 的成员函数,而在组合模式下,GradeCalc 并没有继承 vector,也没有公开 grades 成员或提供 push_back 接口。外部代码无法直接调用 push_back

3.

调用次数:只会调用 1次。第一次调用 info() 时,is_dirty 为 true,会触发 compute() 并将 is_dirty 置为 false。后续两次调用 info() 时,is_dirty 为 false,不会再次调用 compute()

is_dirty的作用:避免在数据未变更的情况下重复进行昂贵的统计计算,是一种性能优化(懒加载/惰性求值)。

新增 update_grade:需要更改。因为 update_grade 会修改 grades 中的数据,导致统计信息失效。因此,在 update_grade 函数的末尾,必须设置 is_dirty = true;,以确保下次调用 info() 时能重新计算

4.

不新增数据成员:可以在需要时(例如在 info() 函数中)临时对 grades 的副本进行排序并计算中位数。

加在哪个函数:加在 info() 函数里最合理,因为它负责输出所有统计信息。

5.

不能去掉 compute 中的 fill(0)

错误场景:假设先录入 [90, 80, 70],调用 info(),此时 counts 为 [0,0,1,1,1]。然后清空成绩(或录入新成绩 [60, 50]),如果 compute 不重置 counts,那么旧的计数 [0,0,1,1,1] 会和新的计数 [1,1,0,0,0] 叠加,得到错误的 [1,1,1,1,1]

6.

对功能有影响吗? 没有。reserve 只是预分配内存容量,不影响逻辑功能。即使去掉,push_back 依然能正常工作。

对性能有影响吗? 有。如果不去掉 reserve(n)vector 在 push_back 过程中很可能不需要重新分配内存(reallocate)。如果去掉,当输入大量成绩时,vector 可能会多次进行内存重新分配和元素拷贝,导致性能下降。

任务二

 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 }
test2.cpp

image

 1.

class GradeCalc: private std::vector<int>

2.

不合法。虽然是继承自 vector,但是 私有继承 (private)。这使得基类 vector 的所有公有和保护成员在 GradeCalc 中都变成了私有成员,因此在类外部无法访问 push_back

3.

组合方式: for(auto grade: grades) // 直接访问私有成员变量 grades

继承方式: for(int grade: *this) // 将 *this(即当前 GradeCalc 对象)当作一个 vector<int> 来使用。

封装差异: 组合提供了更强的封装性,GradeCalc 完全控制对其内部 vector 的访问。继承(即使是私有继承)在类内部实现时,可以像使用基类一样使用 this 指针,但对外部用户而言,两种方式都隐藏了 vector 的接口。

4.

更推荐组合方案。

理由:

语义更清晰:一个成绩计算器 包含 一组成绩,而不是 是一种 vector。组合符合 “has-a” 的自然语义。

封装性更强:组合可以完全控制对 vector 的访问,避免了意外暴露不需要的接口(即使私有继承也可能在类内部误用)。

灵活性更高:如果未来需要更换底层容器(比如换成 list 或自定义容器),组合方案只需修改成员变量类型;而继承方案则需要改变继承关系,改动更大。

任务三

#pragma once

#include <string>
#include <vector>

enum class GraphType {circle, triangle, rectangle};

// Graph类定义
class Graph {
public:
    virtual void draw() {}
    virtual ~Graph() = default;
};

// Circle类声明
class Circle : public Graph {
public:
    void draw();
};

// Triangle类声明
class Triangle : public Graph {
public:
    void draw();
};

// Rectangle类声明
class Rectangle : public Graph {
public:
    void draw();
};

// Canvas类声明
class Canvas {
public:
    void add(const std::string& type);   // 根据字符串添加图形
    void paint() const;                  // 使用统一接口绘制所有图形
    ~Canvas();                           // 手动释放资源

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

// 4. 工具函数
GraphType str_to_GraphType(const std::string& s);  // 字符串转枚举类型
Graph* make_graph(const std::string& type);  // 创建图形,返回堆对象指针
Graph..hpp
 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
#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>

#include "Graph.hpp"

// Circle类实现
void Circle::draw()     { std::cout << "draw a circle...\n"; }

// Triangle类实现
void Triangle::draw()   { std::cout << "draw a triangle...\n"; }

// Rectangle类实现
void Rectangle::draw()  { std::cout << "draw a rectangle...\n"; }

// Canvas类实现
void Canvas::add(const std::string& type) {
    Graph* g = make_graph(type);
    if (g) 
        graphs.push_back(g);
}

void Canvas::paint() const {
    for (Graph* g : graphs) 
        g->draw();   
}

Canvas::~Canvas() {
    for (Graph* g : graphs) 
        delete g;
}

// 工具函数实现
// 字符串 → 枚举转换
GraphType str_to_GraphType(const std::string& s) {
    std::string t = s;
    std::transform(s.begin(), s.end(), t.begin(),
                   [](unsigned char c) { return std::tolower(c);});

    if (t == "circle")   
        return GraphType::circle;

    if (t == "triangle") 
        return GraphType::triangle;

    if (t == "rectangle")
        return GraphType::rectangle;

    return GraphType::circle;   // 缺省返回
}

// 创建图形,返回堆对象指针
Graph* make_graph(const std::string& type) {
    switch (str_to_GraphType(type)) {
    case GraphType::circle:     return new Circle;
    case GraphType::triangle:   return new Triangle;
    case GraphType::rectangle:  return new Rectangle;
    default: return nullptr;
    }
}
Graph.cpp

image

 1.组合: std::vector<Graph*> graphs; // Canvas 类组合了一个图形指针的动态数组,用于存储和管理多个图形对象。

继承:

  1. class Circle: public Graph
  2. class Triangle: public Graph
  3. class Rectangle: public Graph

2.draw 不是虚函数:g->draw() 将静态绑定到 Graph::draw()(即什么都不做),无法实现多态,所有图形都不会被正确绘制。

vector<Graph> (对象切片):会发生 对象切片(Object Slicing)。添加到 vector 中的 CircleTriangle 等对象会被强制转换成 Graph 基类对象,丢失所有派生类特有的数据和行为。paint() 调用的永远是 Graph::draw()

xi构函数不是虚函数:通过基类指针 delete g 时,只会调用 Graph 的析构函数,而不会调用派生类(如 Circle)的析构函数,导致派生类部分资源无法被正确释放,造成内存泄漏或其他未定义行为。

3.Graph.hpp: 声明新类 class Star: public Graph { public: void draw(); };,并在 GraphType 枚举中添加 star

Graph.cpp: 实现 Star::draw() 方法,并在 str_to_GraphType 和 make_graph 函数中添加对 "star" 字符串的支持。

demo3.cpp (测试文件): 可以添加 canvas.add("star"); 来测试新功能。

4.释放位置:在 Canvas 的析构函数 ~Canvas() 中,通过 delete g; 释放。

原始指针利弊:

弊: 手动管理内存容易出错(忘记 delete 导致泄漏,重复 delete 导致崩溃),不符合现代 C++ 的 RAII 原则。

利: 在这个简单示例中,控制权明确,易于理解底层机制。

改进建议:应使用智能指针,如 std::vector<std::unique_ptr<Graph>>,可以自动管理内存,避免上述问题。

任务四

设计一个毛绒玩具工厂系统,能生产不同类型的电子毛绒玩具(如会唱歌的熊、会发光的兔子、会讲故事的猫)。

用户可以通过工厂统一接口查看所有玩具的信息和特异功能继承 (is-a): 定义抽象基类 Toy,所有具体玩具(SingingBearGlowingRabbitStorytellingCat)都继承自它。Toy 包含纯虚函数 virtual void specialFeature() = 0;

组合 (has-a): ToyFactory 类组合一个 std::vector<std::unique_ptr<Toy>> toys 来管理生产的玩具。

 

 

 1 #pragma once
 2 #include <string>
 3 #include <memory>
 4 #include <vector>
 5 #include <iostream>
 6 
 7 // 抽象基类
 8 class Toy {
 9 protected:
10     std::string name;
11     std::string type;
12 
13 public:
14     Toy(const std::string& n, const std::string& t) : name(n), type(t) {}
15     virtual ~Toy() = default;
16 
17     std::string getName() const { return name; }
18     std::string getType() const { return type; }
19 
20     // 纯虚函数,定义特异功能接口
21     virtual void specialFeature() const = 0;
22     virtual void displayInfo() const {
23         std::cout << "名称: " << name << ", 类型: " << type << std::endl;
24         specialFeature();
25     }
26 };
27 
28 // 具体玩具类
29 class SingingBear : public Toy {
30 public:
31     SingingBear(const std::string& name) : Toy(name, "会唱歌的熊") {}
32     void specialFeature() const override {
33         std::cout << "  特异功能: 播放《生日快乐歌》\n";
34     }
35 };
36 
37 class GlowingRabbit : public Toy {
38 public:
39     GlowingRabbit(const std::string& name) : Toy(name, "会发光的兔子") {}
40     void specialFeature() const override {
41         std::cout << "  特异功能: 身体发出柔和的蓝光\n";
42     }
43 };
44 
45 // 工厂类
46 class ToyFactory {
47 private:
48     std::vector<std::unique_ptr<Toy>> toys;
49 
50 public:
51     void addToy(std::unique_ptr<Toy> toy) {
52         toys.push_back(std::move(toy));
53     }
54 
55     void displayAllToys() const {
56         for (const auto& toy : toys) {
57             toy->displayInfo();
58             std::cout << "-------------------\n";
59         }
60     }
61 };
Toy.hpp

 

 1 #include "Toy.hpp"
 2 
 3 int main() {
 4     ToyFactory factory;
 5 
 6     factory.addToy(std::make_unique<SingingBear>("泰迪"));
 7     factory.addToy(std::make_unique<GlowingRabbit>("小白"));
 8 
 9     std::cout << "=== 工厂所有玩具信息 ===\n";
10     factory.displayAllToys();
11 
12     return 0;
13 }
demo4.cpp

image

 

posted @ 2025-12-02 19:16  KXJSLL  阅读(3)  评论(0)    收藏  举报