实验04

任务一

GradeCalc.hpp

#pragma once
#include<array>
#include<string>
#include<vector>
class GradeCalc
{
    public:
        GradeCalc(const std::string &cname);
        void input (int n);
        void output() const;
        void sort(bool ascending=false);
        int min() const;
        int max() const;
        double average() const;
        void info();
        private:
            void compute();
        private:
            std::string course_name;
            std::vector<int>grades;
            std::array<int, 5> counts;
            std::array<double, 5> rates;
            bool is_dirty;
        };

GradeCalc.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=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];
            else if(grade<70)
              ++counts[1];
            else if(grade<80)
              ++counts[2];
            else if(grade<90)
              ++counts[3];
            else
              ++counts[4];
        }
        for(int i=0;i<rates.size();++i)
           rates[i]=counts[i]*1.0/grades.size();
        is_dirty=false;
}
#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=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];
            else if(grade<70)
              ++counts[1];
            else if(grade<80)
              ++counts[2];
            else if(grade<90)
              ++counts[3];
            else
              ++counts[4];
        }
        for(int i=0;i<rates.size();++i)
           rates[i]=counts[i]*1.0/grades.size();
        is_dirty=false;
}

demo1.cpp

#include <iostream>
#include <string>
#include "GradeCalc.hpp"
 void test()
  {
    GradeCalc c1("OOP");
    std::cout << "录入成绩:\n";
    c1.input(5);
    std::cout << "输出成绩:\n";
    c1.output();
    std::cout << "排序后成绩:\n";
    c1.sort(); c1.output();
     std::cout << "*************成绩统计信息*************\n";
    c1.info();
 }
 int main()
 {
    test();
 }

运行结果截图:

image

 

问题1:组合关系识别

GradeCalc 类声明中,逐行写出所有体现"组合"关系的成员声明,并用一句话说明每个被组合对象的功能。
答:std::string course_name,功能是存储课程名
     std::vector<int> grades,功能是存储成绩
     std::array<int, 5> counts,功能是统计人数
     std::array<double, 5> rates,存各分段百分率
问题2:接口暴露理解
如在 test 模块中这样使用,是否合法?如不合法,解释原因。
不合法,因为GradeCalc与vector<int>是组合关系,GradeCalc并没有继承他,不可以直接调push_back。
问题3:架构设计分析
当前设计方案中, compute 在 info 模块中调用:
(1)连续打印3次统计信息, compute 会被调用几次?标记 is_dirty 起到什么作用?
 答:1次。避免重复做一样的计算。
(2)如新增 update_grade(index, new_grade) ,这种设计需要更改 compute 调用位置吗?简洁说明理由。
 答:要。只要在 update_grade 中设置 is_dirty=true,下一次 info() 自动触发 compute。
问题4:功能扩展设计
要增加"中位数"统计,不新增数据成员怎么做?在哪个函数里加?写出伪代码。
答:在sort()中增加伪代码:
         int n=grades.size();
         if(n%2)
         min=grades[n/2];
         else
         min=(grades[n/2]+grades[n/2-1])/2.0
问题5:数据状态管理
GradeCalc 和 compute 中都包含代码: counts.fill(0); rates.fill(0); 。compute 中能否去掉这两行?如去掉,在哪种使用场景下会引发统计错误?

不能去掉。如果去掉,当第二次录入成绩,再次调用 compute ,counts 会累积上一次统计的数据,出现统计错误。

问题6:内存管理理解
input 模块中代码 grades.reserve(n); 如果去掉:
(1)对程序功能有影响吗?(去掉重新编译、运行,观察功能是否受影响)
答:没有。
(2)对性能有影响吗?如有影响,用一句话陈述具体影响。

有影响。reserve(n) 预分配空间,可减少扩容次数,提高效率。

实验任务2:

GradeCalc2.hpp

#pragma once
#include<array>
#include<string>
#include<vector>
class GradeCalc:private std::vector<int>
{
    public:
          GradeCalc(const std::string &cname);
          void input(int n);
          void output() const;
          void sort(bool ascending=false);
          int min() const;
          int max() const;
          double average() const;
          void info();
    private:
          void compute();
    private:
          std::string course_name;
          std::array<int, 5> counts;
          std::array<double, 5> rates;
          bool is_dirty;
 };

GradeCalc2.cpp

#include <algorithm>
#include <array>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>
#include "GradeCalc2.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";
        return;
       }
   this->reserve(n);
  int grade;
  for(int i=0;i<n;)
  {
      std::cin>>grade;
      if(grade<0||grade>100)
     {
         std::cerr<< "无效输入! 分数须在[0,100]\n";
         continue;
     }
     this->push_back(grade);
     ++i;
   }
   is_dirty=true;
}
 void GradeCalc::output() const
 {
     for(auto grade:*this)
        std::cout<<grade<<' ';
    std::cout<<std::endl;
  }
  void GradeCalc::sort(bool ascending)
  {
      if(ascending)
         std::sort(this->begin(),this->end());
      else
        std::sort(this->begin(),this->end(),std::greater<int>());
  }
  int GradeCalc::min() const
  {
      if(this->empty())
        return -1;
      return *std::min_element(this->begin(),this->end());
  }
  int GradeCalc::max() const
  {
      if(this->empty())
      return -1;
    return *std::max_element(this->begin(),this->end());
   }
   double GradeCalc::average() const
   {
       if(this->empty())
         return 0.0;
       double avg = std::accumulate(this->begin(), this->end(), 0.0) / this->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 = 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(this->empty())
        return;
     counts.fill(0);
    rates.fill(0);
    for(int grade: *this)
    {
        if(grade < 60)
            ++counts[0];
        else if (grade < 70)
            ++counts[1];
        else if (grade < 80)
            ++counts[2];
        else if (grade < 90)
            ++counts[3];
        else
            ++counts[4];
        }
    for(int i=0;i<rates.size();++i)
       rates[i]=counts[i]*1.0/this->size();
    is_dirty=false;
 }

demo2.cpp

#include <iostream>
#include <string>
#include "GradeCalc.hpp"
 void test() {
    GradeCalc c1("OOP");
    std::cout << "录入成绩:\n";
    c1.input(5);
    std::cout << "输出成绩:\n";
    c1.output();
    std::cout << "排序后成绩:\n";
    c1.sort(); c1.output();
    std::cout << "*************成绩统计信息*************\n";
    c1.info();
 }
 int main() {
    test();
 }

实验运行结果截图:

 

image

 

问题1:继承关系识别
写出 GradeCalc 类声明体现"继承"关系的完整代码行。

class GradeCalc : private std::vector<int>

问题2:接口暴露理解
当前继承方式下,基类 vector<int> 的接口会自动成为 GradeCalc 的接口吗?如在 test 模块中这样用,能否编译通过?用一句话解释原因。
不会。否。因为采用 private 继承,基类所有 public / protected 成员都变成 GradeCalc 的 private 成员,对外不可见。
问题3:数据访问差异
对比继承方式与组合方式内部实现数据访问的一行典型代码。说明两种方式下的封装差异带来的数据访问接口差异。
组合方式:只能通过grades这个成员变量访问数据
继承方式:可以直接通过this指针访问
问题4:组合 vs. 继承方案选择
你认为组合方案和继承方案,哪个更适合成绩计算这个问题场景?简洁陈述你的结论和理由
答:我认为组合更合适,组合方式只需修改内部实现,封装性更强,不会暴露公共接口
 
实验任务3:
Graph.hpp
#pragma once
#include<string>
#include<vector>
enum class GraphType{circle,triangle,rectangle};
class Graph
{
    public:
    virtual void draw(){}
    virtual ~Graph()=default;
};
class Circle:public Graph
{
    public:
        void draw();
 };
 class Triangle : public Graph
{
    public:
        void draw();
 };
class Rectangle:public Graph
{
    public:
         void draw();
 };
class Canvas
{
    public:
        void add(const std::string& type);
        void paint() const;
        ~Canvas();
    private:
        std::vector<Graph*>graphs;
};
GraphType str_to_GraphType(const std::string& s);
Graph* make_graph(const std::string& type);

Graph.cpp

#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>
#include "Graph.hpp"
void Circle::draw()
{
  std::cout<<"draw a circle...\n";
}
void Triangle::draw()
{
  std::cout<<"draw a triangle...\n";
 }
void Rectangle::draw()
{
  std::cout<<"draw a rectangle...\n";
 }
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;
    }
}

demo3.cpp

#include <string>
#include "Graph.hpp"
 void test() {
    Canvas canvas;
    canvas.add("circle");
    canvas.add("triangle");
    canvas.add("rectangle");
    canvas.paint();
 }
 int main()
 {
    test();
 }

实验运行结果截图:

image

 

问题1:对象关系识别
(1)写出Graph.hpp中体现"组合"关系的成员声明代码行,并用一句话说明被组合对象的功能。
 答:std::vector<Graph*>graphs;Canvas 包含 多个图形对象,功能存储多个图形对象的指针。
(2)写出Graph.hpp中体现"继承"关系的类声明代码行。
答:class Circle : public Graph { ... };
 class Triangle : public Graph { ... };
 class Rectangle : public Graph { ... };
问题2:多态机制观察
(1) Graph 中的 draw 若未声明成虚函数, Canvas::paint() 中 g->draw() 运行结果会有何不同?
会执行 Graph的 draw,而不会执行子类重写的 draw。
(2)若 Canvas 类 std::vector<Graph*> 改成 std::vector<Graph> ,会出现什么问题?
丧失多态性,draw()调用都输出为空。
(3)若 ~Graph() 未声明成虚函数,会带来什么问题?
内存泄漏,空间无法得到释放。
问题3:扩展性思考
若要新增星形 Star ,需在哪些文件做哪些改动?逐一列出。
添加新的类声明

class Star : public Graph {
public:
void draw() override;
};

并在枚举中加入

enum class GraphType { circle, triangle, rectangle, star };

Graph.cpp中添加Star::draw()实现;make_graph中添加分支case GraphType::star: return new Star; 在str_to_GraphType函数中新增Star字符串转换为枚举类型功能 

问题4:资源管理
观察 make_graph 函数和 Canvas 析构函数:
(1) make_graph 返回的对象在什么地方被释放?
for (Graph* g : graphs) delete g
(2)使用原始指针管理内存有何利弊?
优点:简单直观,更容易理解
缺点:必须手动delete,容易造成内存泄漏,容易产生野指针。
实验任务四:
问题描述:
这次任务大概意思就是让设计一个“玩具工厂”,里面有各种毛绒玩具,每个玩具都有自己的特异功能,比如说熊会拥抱、猫会卖萌、机器人(颗粒感来源昨天刷的视频“)之类的。

然后让工厂统一调用这些玩具的“功能接口”,就是不管是什么玩具,都能通过同一个函数来触发它们的技能。

对象关系:

Toy(基类)

  • 所有玩具都有名字、类型

  • 有一个虚函数 special(),每种玩具重写它

Bear / Cat / Robot(派生类)

分别重写自己的技能,比如拥抱、卖萌等

ToyFactory(组合)

  • 工厂里面放一堆 Toy*(这里用了组合)

  • 工厂有 add 来添加玩具

  • show_all() 来展示所有玩具的技能

Toy.hpp
#pragma once
#include <string>
#include <vector>
#include <iostream>

class Toy {
public:
    Toy(const std::string& name, const std::string& type)
        : name(name), type(type) {}

virtual void special() { std::cout << name << " has no special ability.\n"; } virtual void info() { std::cout << "玩具名称: " << name << " 类型: " << type << "\n"; } virtual ~Toy() = default; protected: std::string name; std::string type; }; // class Bear : public Toy { public: Bear(const std::string& name) : Toy(name, "") {} void special() override { std::cout << name << "请给我一个拥抱!\n"; } }; // class Cat : public Toy { public: Cat(const std::string& name) : Toy(name, "") {} void special() override { std::cout << name << "喵\n"; } }; // 机器人 class Robot : public Toy { public: Robot(const std::string& name) : Toy(name, "机器人") {} void special() override { std::cout << name << " 颗粒感\n"; } };

ToyFactory.hpp

#pragma once
#include "Toy.hpp"

class ToyFactory {
public:
    ~ToyFactory() {
        for (auto p : toys) 
            delete p;
    }

    void add(Toy* toy) {
        toys.push_back(toy);
    }

    void show_all() const {
        for (auto t : toys) {
            t->info();
            t->special();
            std::cout << "---------------------\n";
        }
    }

private:
    std::vector<Toy*> toys;
};

demo4.cpp

#include "ToyFactory.hpp"

int main() {
    ToyFactory factory;

    factory.add(new Bear("熊大"));
    factory.add(new Cat("小花猫"));
    factory.add(new Robot("小R"));

    factory.show_all();

    return 0;
}

运行结果

image

 

 
 
posted @ 2025-12-03 00:35  myFuji1  阅读(0)  评论(0)    收藏  举报