实验四

任务一

任务一源代码:

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

GradeClac.cpp

#include <algorithm>
#include <array>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>

#include "GradeCalc.hpp"

GradeCalc::GradeCalc(const std::string& cname) :course_name{ cname }, is_dirty{ true } {
    counts.fill(0);
    rates.fill(0);
}

void GradeCalc::input(int n) {
    if (n < 0) {
        std::cerr << "无效输入! 人数不能为负数\n";
        std::exit(1);
    }

    grades.reserve(n);

    int grade;

    for (int i = 0; i < n;) {
        std::cin >> grade;

        if (grade < 0 || grade > 100) {
            std::cerr << "无效输入! 分数须在[0,100]\n";
            continue;
        }

        grades.push_back(grade);
        ++i;
    }

    is_dirty = true;  // 设置脏标记:成绩信息有变更
}

void GradeCalc::output() const {
    for (auto grade : grades)
        std::cout << grade << ' ';
    std::cout << std::endl;
}

void GradeCalc::sort(bool ascending) {
    if (ascending)
        std::sort(grades.begin(), grades.end());
    else
        std::sort(grades.begin(), grades.end(), std::greater<int>());
}

int GradeCalc::min() const {
    if (grades.empty())
        return -1;

    auto it = std::min_element(grades.begin(), grades.end());
    return *it;
}

int GradeCalc::max() const {
    if (grades.empty())
        return -1;

    auto it = std::max_element(grades.begin(), grades.end());
    return *it;
}

double GradeCalc::average() const {
    if (grades.empty())
        return 0.0;

    double avg = std::accumulate(grades.begin(), grades.end(), 0.0) / grades.size();
    return avg;
}

void GradeCalc::info() {
    if (is_dirty)
        compute();

    std::cout << "课程名称:\t" << course_name << std::endl;
    std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() << std::endl;
    std::cout << "最高分:\t" << max() << std::endl;
    std::cout << "最低分:\t" << min() << std::endl;

    const std::array<std::string, 5> grade_range{ "[0, 60) ",
                                           "[60, 70)",
                                           "[70, 80)",
                                           "[80, 90)",
                                           "[90, 100]" };

    for (int i = static_cast<int>(grade_range.size()) - 1; i >= 0; --i)
        std::cout << grade_range[i] << "\t: " << counts[i] << "人\t"
        << std::fixed << std::setprecision(2) << rates[i] * 100 << "%\n";
}

void GradeCalc::compute() {
    if (grades.empty())
        return;

    counts.fill(0);
    rates.fill(0.0);

    // 统计各分数段人数
    for (auto grade : grades) {
        if (grade < 60)
            ++counts[0];        // [0, 60)
        else if (grade < 70)
            ++counts[1];        // [60, 70)
        else if (grade < 80)
            ++counts[2];        // [70, 80)
        else if (grade < 90)
            ++counts[3];        // [80, 90)
        else
            ++counts[4];        // [90, 100]
    }

    // 统计各分数段比例
    for (size_t i = 0; i < rates.size(); ++i)
        rates[i] = counts[i] * 1.0 / grades.size();

    is_dirty = false;  // 更新脏标记
}
 1 #include <iostream>
 2 #include <memory>
 3 #include "Toy.hpp"
 4 
 5 void test() {
 6     // 创建玩具工厂
 7     ToyFactory factory;
 8 
 9     // 添加不同类型的玩具到工厂(智能指针管理内存)
10     factory.add_toy(std::make_unique<MUSICIAN>("音乐家", 85,":儿歌\n"));
11     factory.add_toy(std::make_unique<ROBOT>("机器人", 75,  ":黄色\n"));
12     factory.add_toy(std::make_unique<DINOSOUR>("恐龙", 100,true));
13 
14     // 显示所有玩具信息
15     factory.show_toys();
16 
17     // 执行所有玩具的特异功能(一个接口,多种行为)
18     factory.run_specialfunc();
19 }
20 
21 int main() {
22     test();
23     return 0;
24 }

任务1截图:

屏幕截图 2025-12-01 225753

任务1问题:

问题1:
std::string course_name; 功能:存储课程名称
std::vector<int> grades; 功能:储多个课程成绩
std::array<int, 5> counts; 功能:存储 5 个分数段的人数
std::array<double, 5> rates; 功能:存储 5 个分数段的人数占比

问题2:
不能,grades是类的私有成员,test函数无法通过push_back调用、

问题3:
调用一次
is_dirty作用是通过其false和true值跳过computer避免重复计算
不需要,update_grade作用是修改指定索引的成绩,只需要在update_grade函数内设置is_dirty = true即可

问题4:
应该在sort函数里加,因为已经排序好:
伪代码
double miu;
int n = grades.size();
if (n % 2 == 0)
{
miu = (grades[n / 2] + grades[n / 2 - 1])
}
else;
miu = grades[n / 2]
std::cout << "中位数为" << miu << endl;

问题5:
不可以去掉,
在成绩信息发生改变的时候再次调用info会发生错误

问题6:
对功能无影响
对性能有影响,vector会多次扩容,增加内存分配

 

任务2源代码:

GradeClac.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 };

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 }

task2.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 }
25 
26 
27 //
28 问题1:
29 class GradeCalc : private std::vector<int>
30     不会自动成为接口并且test模块不会通过,因为这个继承是private继承,在类外部完全不可见
31     组合方式内部要通过变量名访问,而继承方式内部可直接用this->来访问
32 
33     组合的方式更好,因为组合方式的封装性更好,在组合方式下vector完全作为内部实现细节,而继承方式仍存在在内部操作的风险

任务二截图:

屏幕截图 2025-12-02 171316

任务2问题:


问题1:
class GradeCalc : private std::vector<int>
问题2:

不会自动成为接口并且test模块不会通过,因为这个继承是private继承,在类外部完全不可见
问题3:

组合方式内部要通过变量名访问,而继承方式内部可直接用this->来访问

问题4:
组合的方式更好,因为组合方式的封装性更好,在组合方式下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);  // 创建图形,返回堆对象指针

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 }

task3.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 }

任务3截图:
屏幕截图 2025-12-02 172619

任务3问题:

问题1:
std::vector<Graph*> graphs;存储多个图形对象
class Circle : public Graph {
class Triangle : public Graph {
class Rectangle : public Graph {

问题2:
Canvas::paint() 中 g->draw()会没有任何输出
会无法编译
派生类的析构函数不会被调用

问题3:
在Graph.hpp中,新增class Star:public Graph声明,并在Star类中声明void draw
在Graph.cpp中,实现Star::draw,修改str_to_GraphType,新增“star”字符串
修改make_graph,新增case GraphType::star:return new Star
问题4:
在Canvas的析构函数中被释放

优点:没有智能指针的开销,并且可以手动控制内存的生命周期
缺点:容易重复释放,也容易导致内存泄露,没有智能指针的安全性高

 

任务4源代码:

Toy.hpp:

 1 #pragma once
 2 #include<string>
 3 #include<vector>
 4 #include <iostream>
 5 #include<memory>
 6 
 7 
 8 class Toy {
 9 public:
10     //构造函数
11  Toy(const std::string& name,const std::string& type,int battery)
12      :toy_name(name),toy_type(type),battery(battery){ }
13     //纯虚函数:特异功能
14  virtual void specialfunc() = 0;
15  //纯虚函数:显示玩具信息
16  virtual void showinfo() const = 0;
17  //虚析构函数
18  virtual ~Toy() = default;
19 
20  //通用接口:电量检测:
21  void check_battery()const {
22      std::cout << "[" << toy_name << "]当前电量:" << battery << "%"<<std::endl;
23 
24  }
25 protected:
26     std::string toy_name; //玩具名称
27     std::string toy_type; //玩具类型
28     int battery;
29 };
30 
31 class MUSICIAN :public Toy {
32 public:
33     MUSICIAN(const std::string&name,int battery,const std::string& sound_type)
34         :Toy(name,"发声",battery),sound_type(sound_type){ }
35      
36     void specialfunc();
37     void showinfo()const override;
38 private:
39     std::string sound_type;
40 
41 };
42 
43 class ROBOT :public Toy {
44 public:
45     ROBOT(const std::string& name, int battery,const std::string& light_type )
46         :Toy(name,"发光",battery),light_type(light_type){ }
47 
48     void specialfunc();
49     void showinfo()const override;
50 private:
51     std::string light_type;
52 };
53 
54 class DINOSOUR :public Toy {
55     
56 public:
57     DINOSOUR(const std::string& name, int battery,bool can_move)
58         :Toy(name,"运动",battery),can_move(can_move){ }
59 
60     void specialfunc();
61     void showinfo()const override;
62 private:
63     bool can_move;
64 
65 };
66 
67 
68 class ToyFactory {
69 public:
70     // 添加玩具到工厂
71     void add_toy(std::unique_ptr<Toy> toy);
72     //显示所有玩具信息
73     void show_toys();
74     void run_specialfunc(); 
75 private:
76     std::vector<std::unique_ptr<Toy>> toys; // 组合关系:工厂包含多个玩具对
77 };

Toy.cpp:

 1 #include<iostream>
 2 #include<iomanip>
 3 #include"Toy.hpp"
 4 
 5 //MUSICIAN特异功能实现
 6 void MUSICIAN::specialfunc()
 7 {
 8     std::cout << "[" << toy_name << "]MUSICIAN特异功能:";
 9     std::cout << "清了清嗓子," << sound_type;
10 
11 }
12 void MUSICIAN::showinfo() const {
13     std::cout << "======玩具信息======\n";
14     std::cout << "名称: " << toy_name << "\n类型:" << toy_type << "\n电量" << battery << "%\n";
15     std::cout << "发声类型" << sound_type << "\n";
16 }
17 
18 //ROBOT特异功能实现
19 
20 void ROBOT::specialfunc() {
21     std::cout << "[" << toy_name << "]ROBOT特异功能:";
22     std::cout << "拍了拍身体," << light_type;
23 }
24 void ROBOT::showinfo() const {
25     std::cout << "======玩具信息======\n";
26     std::cout << "名称: " << toy_name << "\n类型:" << toy_type << "\n电量" << battery << "%\n";
27     std::cout << "发光类型" << light_type << "\n";
28 }
29 
30 //DINOSOUR特异功能实现
31 
32 void DINOSOUR::specialfunc() {
33     std::cout << "[" << toy_name << "]DINOSOUR特异功能:";
34     
35     if (can_move)
36         std::cout << "摇了摇脑袋,向前走\n";
37     else
38         std::cout << "发出嘶吼声\n";
39 }
40 void DINOSOUR::showinfo() const {
41     std::cout << "======玩具信息======\n";
42     std::cout << "名称: " << toy_name << "\n类型:" << toy_type << "\n电量" << battery << "%\n";
43     std::cout << "能否运动" << (can_move ? "可移动" : "不可移动") << "\n";
44 }
45 
46 
47 // 玩具工厂:添加玩具
48 void ToyFactory::add_toy(std::unique_ptr<Toy> toy) {
49     if (toy) {
50         toys.push_back(std::move(toy));
51     }
52     else {
53         std::cerr << "添加玩具失败:无效的玩具对象!\n";
54     }
55 }
56 
57 //玩具工厂:显示所有玩具信息
58 
59 void ToyFactory::show_toys() {
60 
61     std::cout << "======玩具工厂所有玩具======\n";
62         for (const auto& toy : toys) {
63             toy->showinfo();
64             toy->check_battery(); // 调用通用接口:电量检测
65             std::cout << "======================================\n";
66         }
67 
68 }
69 
70 
71 //玩具工厂:执行玩具的特异功能
72 void ToyFactory::run_specialfunc() {
73     std::cout<<"\n=======执行所有玩具特异功能=======\n";
74     for (const auto& toy : toys) {
75         toy->specialfunc();
76     }
77 }

demo4:

 1 #include <iostream>
 2 #include <memory>
 3 #include "Toy.hpp"
 4 
 5 void test() {
 6     // 创建玩具工厂
 7     ToyFactory factory;
 8 
 9     // 添加不同类型的玩具到工厂(智能指针管理内存)
10     factory.add_toy(std::make_unique<MUSICIAN>("音乐家", 85,":儿歌\n"));
11     factory.add_toy(std::make_unique<ROBOT>("机器人", 75,  ":黄色\n"));
12     factory.add_toy(std::make_unique<DINOSOUR>("恐龙", 100,true));
13 
14     // 显示所有玩具信息
15     factory.show_toys();
16 
17     // 执行所有玩具的特异功能(一个接口,多种行为)
18     factory.run_specialfunc();
19 }
20 
21 int main() {
22     test();
23     return 0;
24 }

任务4截图:

屏幕截图 2025-12-02 202921

对象关系:

本设计核心运用继承(多态) 和组合两大对象关系,抽象基类 Toy 为父类,MUSICIAN,ROBOT,DINOSOUR为子类,

组合关系:ToyFactory为整体,Toy 子类对象为部分;

 

总结:任务四中遇到了很大困难,借助ai我学会了智能指针的使用,通过智能指针std::unique_ptr<Toy>

另外起初我对虚函数只存在表面的理解,只知道用于实现多态,为其他类提供接口,通过实验四我学会了如何写虚函数

在写代码时遇到了派生类 showinfo 函数返回值 /const 修饰不匹配的问题,知道了派生类重写虚函数时,const修饰必须与基类一致。

posted @ 2025-12-02 21:19  杨启霖  阅读(7)  评论(0)    收藏  举报