实验四

任务一:

GradeCalc.hpp

 1 #pragma once
 2 #include<array>
 3 #include<string>
 4 #include<vector>
 5 class GradeCalc
 6 {
 7     public:
 8         GradeCalc(const std::string &cname);
 9         void input (int n);
10         void output() const;
11         void sort(bool ascending=false);
12         int min() const;
13         int max() const;
14         double average() const;
15         void info();
16         private:
17             void compute();
18         private:
19             std::string course_name;
20             std::vector<int>grades;         
21             std::array<int, 5> counts;
22             std::array<double, 5> rates;
23             bool is_dirty;
24         };
25         

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 GradeCalc::GradeCalc(const std::string &cname):course_name{cname},is_dirty{true}
 11 {
 12     counts.fill(0);
 13     rates.fill(0);
 14 }
 15 void GradeCalc::input(int n)
 16 {
 17     if(n<0)
 18     {
 19         std::cerr<<"无效输入!人数不能为负数\n";
 20         std::exit(1);
 21     }
 22     grades.reserve(n);
 23     int grade;
 24     for(int i=0;i<n;)
 25     {
 26         std::cin>>grade;
 27         if(grade<0||grade>100)
 28           {
 29               std::cerr<<"无效输入!分数须在[0,100]\n";
 30               continue;
 31           }
 32           grades.push_back(grade);
 33           ++i;
 34         }    
 35         is_dirty=true;  
 36 }
 37     void GradeCalc::output() const
 38     {
 39         for(auto grade:grades)
 40           std::cout<<grade<<' ';
 41           std::cout<<std::endl;      
 42      }
 43      
 44      void GradeCalc::sort(bool ascending)
 45      {
 46          if(ascending)
 47             std::sort(grades.begin(),grades.end());
 48          else
 49            std::sort(grades.begin(),grades.end(),std::greater<int>());           
 50      }
 51      int GradeCalc::min() const
 52      {
 53          if(grades.empty())
 54            return -1;
 55          auto it=std::min_element(grades.begin(),grades.end());       
 56         return*it;
 57       } 
 58       int  GradeCalc::max() const
 59       {
 60           if(grades.empty())
 61              return -1;
 62       auto it=std::max_element(grades.begin(),grades.end());
 63       return *it;       
 64       }
 65     double GradeCalc::average() const
 66     {
 67         if(grades.empty())
 68            return 0.0;
 69         double avg=std::accumulate(grades.begin(),grades.end(),0.0)/grades.size();
 70         return avg;       
 71       }  
 72     void::GradeCalc::info()
 73     {
 74         if(is_dirty)
 75            compute();
 76         std::cout<<"课程名称:\t"<<course_name<<std::endl;
 77         std::cout<<"平均分:\t"<<std::fixed<<std::setprecision(2)<<average()<<std::endl;
 78         std::cout<<"最高分:\t" << max() << std::endl;
 79         std::cout<<"最低分:\t" << min() << std::endl;
 80         const std::array<std::string, 5>grade_range{"[0 , 60)","[60, 70)","[70, 80)","[80, 90)","[90, 100]"};
 81         for(int i=grade_range.size()-1;i>=0;--i)
 82             std::cout<<grade_range[i]<<"\t :"<<counts[i]<<"人\t"<<std::fixed<<std::setprecision(2)<<rates[i]*100<<"%\n";     
 83       } 
 84         void GradeCalc::compute()
 85         {
 86             if(grades.empty())
 87                return;
 88             counts.fill(0);
 89             rates.fill(0.0);   
 90         for(auto grade:grades) 
 91         {
 92             if(grade<60)
 93               ++counts[0];
 94             else if(grade<70)
 95               ++counts[1];
 96             else if(grade<80)
 97               ++counts[2];
 98             else if(grade<90)
 99               ++counts[3];
100             else  
101               ++counts[4];         
102         }
103         for(int i=0;i<rates.size();++i)
104            rates[i]=counts[i]*1.0/grades.size();
105         is_dirty=false;
106 }

demo1.cpp

 1 #include <iostream>
 2 #include <string>
 3 #include "GradeCalc.hpp"
 4  void test()
 5   {
 6     GradeCalc c1("OOP");
 7     std::cout << "录入成绩:\n";
 8     c1.input(5);
 9     std::cout << "输出成绩:\n";
10     c1.output();
11     std::cout << "排序后成绩:\n";
12     c1.sort(); c1.output();
13      std::cout << "*************成绩统计信息*************\n";
14     c1.info();
15  }
16  int main() 
17  {
18     test();
19  }

运行结果截图

实验一

问题1:组合关系识别
GradeCalc 类声明中,逐行写出所有体现"组合"关系的成员声明,并用一句话说明每个被组合对象的功能。
  答:std::string course_name,功能是存储课程名
         std::vector<int> grades,功能是存储成绩
         std::array<int, 5> counts,功能是统计人数
         std::array<double, 5> rates,存各分段百分率
问题2:接口暴露理解
当前继承方式下,基类vector<int> 的接口会自动成为GradeCalc 的接口吗?
  答:不会
如在test模块中这样使用,是否合法?如不合法,解释原因。
  答:不合法,因为GradeCalc与vector<int>是组合关系,不是继承关系,不可以直接调用push_back.
问题3:架构设计分析
当前设计方案中,compute 在info 模块中调用:
(1)连续打印3次统计信息,compute 会被调用几次?标记is_dirty 起到什么作用?
  答:调用1次,避免重复做一样的计算
(2)如新增update_grade(index, new_grade) ,这种设计需要更改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 中能否去掉这两行?如去掉,在哪种使用场景下会引发统计错误?
 答:不能,在多次调用input()情况下会发生错误
问题6:内存管理理解
input 模块中代码grades.reserve(n); 如果去掉:
(1)对程序功能有影响吗?(去掉重新编译、运行,观察功能是否受影响)
   答:不会有影响
(2)对性能有影响吗?如有影响,用一句话陈述具体影响。
   答:会。reserve()是预分配内存,没有的话,当vector容量不足时需重新分配,降低效率。

 任务二

GradeCalc2.hpp

 1 #pragma once
 2 #include<array>
 3 #include<string>
 4 #include<vector>
 5 class GradeCalc:private std::vector<int>
 6 {
 7     public:
 8           GradeCalc(const std::string &cname);
 9           void input(int n);
10           void output() const;
11           void sort(bool ascending=false);
12           int min() const;
13           int max() const;
14           double average() const;
15           void info();
16     private:
17           void compute();
18     private:
19           std::string course_name;
20           std::array<int, 5> counts;
21           std::array<double, 5> rates;
22           bool is_dirty;            
23  };

GradeCalc2.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 "GradeCalc2.hpp"
 10  GradeCalc ::GradeCalc(const std::string &cname):course_name{cname},is_dirty{true}
 11  {
 12     counts.fill(0);
 13     rates.fill(0);
 14  } 
 15  void GradeCalc::input(int n)
 16  {
 17      if(n<0)
 18       {
 19           std::cerr<<"无效输入! 人数不能为负数\n";
 20         return;      
 21        } 
 22    this->reserve(n);
 23   int grade;
 24   for(int i=0;i<n;)
 25   {
 26       std::cin>>grade;
 27       if(grade<0||grade>100)
 28      {
 29          std::cerr<< "无效输入! 分数须在[0,100]\n";
 30          continue;
 31      }
 32      this->push_back(grade);
 33      ++i;
 34    } 
 35    is_dirty=true;
 36 }
 37  void GradeCalc::output() const
 38  {
 39      for(auto grade:*this)
 40         std::cout<<grade<<' ';
 41     std::cout<<std::endl;     
 42   } 
 43   void GradeCalc::sort(bool ascending)
 44   {
 45       if(ascending)
 46          std::sort(this->begin(),this->end());
 47       else
 48         std::sort(this->begin(),this->end(),std::greater<int>());  
 49   }
 50   int GradeCalc::min() const
 51   {
 52       if(this->empty())
 53         return -1;
 54       return *std::min_element(this->begin(),this->end());  
 55   }
 56   int GradeCalc::max() const
 57   {
 58       if(this->empty())
 59       return -1;
 60     return *std::max_element(this->begin(),this->end()); 
 61    }
 62    double GradeCalc::average() const
 63    {
 64        if(this->empty())
 65          return 0.0;
 66        double avg = std::accumulate(this->begin(), this->end(), 0.0) / this->size();
 67     return avg;  
 68     } 
 69     void GradeCalc::info()
 70     {
 71         if(is_dirty)
 72            compute();
 73 
 74     std::cout << "课程名称:\t" << course_name << std::endl;
 75     std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() << std::endl;
 76     std::cout << "最高分:\t" << max() << std::endl;
 77     std::cout << "最低分:\t" << min() << std::endl;
 78     const std::array<std::string, 5> grade_range{"[0 , 60)","[60, 70)","[70, 80)","[80, 90)","[90,100]"};                  
 79     for(int i = grade_range.size()-1; i >= 0; --i)
 80         std::cout << grade_range[i] << "\t: " << counts[i] << "人\t"<< std::fixed << std::setprecision(2) <<rates[i]*100 << "%\n";
 81  }
 82  void GradeCalc::compute()
 83  {
 84      if(this->empty())
 85         return;
 86      counts.fill(0);
 87     rates.fill(0);
 88     for(int grade: *this)
 89     {
 90         if(grade < 60)
 91             ++counts[0];        
 92         else if (grade < 70)
 93             ++counts[1];        
 94         else if (grade < 80)
 95             ++counts[2];        
 96         else if (grade < 90)
 97             ++counts[3];        
 98         else
 99             ++counts[4];       
100         }  
101     for(int i=0;i<rates.size();++i)
102        rates[i]=counts[i]*1.0/this->size();
103     is_dirty=false;         
104  }

demo2.cpp

 1 #include <iostream>
 2 #include <string>
 3 #include "GradeCalc.hpp"
 4  void test() {
 5     GradeCalc c1("OOP");
 6     std::cout << "录入成绩:\n";
 7     c1.input(5);
 8     std::cout << "输出成绩:\n";
 9     c1.output();
10     std::cout << "排序后成绩:\n";
11     c1.sort(); c1.output();
12     std::cout << "*************成绩统计信息*************\n";
13     c1.info();
14  }
15  int main() {
16     test();
17  }
18  

运行结果截图

实验二

问题1:继承关系识别
写出GradeCalc 类声明体现"继承"关系的完整代码行。
  答:class GradeCalc:private std::vector<int>
问题2:接口暴露理解
当前继承方式下,基类vector<int> 的接口会自动成为GradeCalc 的接口吗?
  答:不会,因为是私有继承
如在test模块中这样用,能否编译通过?用一句话解释原因。
  答:不能,私有继承vector,push_back()在GradeCalc中变为private 外部无法访问
问题3:数据访问差异
对比继承方式与组合方式内部实现数据访问的一行典型代码。说明两种方式下的封装差异带来的数据访
问接口差异。
  答:组合方式通过grades访问数据,继承方式通过this指针访问
问题4:组合 vs. 继承方案选择
你认为组合方案和继承方案,哪个更适合成绩计算这个问题场景?简洁陈述你的结论和理由。
  答:组合更适合。组合方式只需修改内部实现,不影响公共接口,继承更加复杂,影响接口

 任务三

Graph.hpp

 1 #pragma once
 2 #include<string>
 3 #include<vector>
 4 enum class GraphType{circle,triangle,rectangle};
 5 class Graph
 6 {
 7     public:
 8     virtual void draw(){}
 9     virtual ~Graph()=default; 
10 };
11 class Circle:public Graph
12 {
13     public:
14         void draw();
15  };
16  class Triangle : public Graph
17 {
18     public:
19         void draw();
20  };
21 class Rectangle:public Graph
22 {
23     public:
24          void draw();
25  };
26 class Canvas
27 {
28     public:
29         void add(const std::string& type);
30         void paint() const;
31         ~Canvas();
32     private:
33         std::vector<Graph*>graphs;
34 };
35 GraphType str_to_GraphType(const std::string& s);
36 Graph* make_graph(const std::string& type);

Graph.cpp

 1 #include <algorithm>
 2 #include <cctype>
 3 #include <iostream>
 4 #include <string>
 5 #include "Graph.hpp"
 6 void Circle::draw()
 7 {
 8   std::cout<<"draw a circle...\n";
 9 }
10 void Triangle::draw()
11 {
12   std::cout<<"draw a triangle...\n";  
13  } 
14 void Rectangle::draw()
15 {
16   std::cout<<"draw a rectangle...\n";    
17  } 
18 void Canvas::add(const std::string& type)
19 {
20   Graph* g=make_graph(type);
21   if(g)
22      graphs.push_back(g);
23  } 
24 void Canvas::paint() const
25 {
26     for(Graph* g:graphs)
27       g->draw();
28  } 
29 Canvas::~Canvas()
30 {
31     for(Graph* g:graphs)
32      delete g;
33  } 
34 GraphType str_to_GraphType(const std::string& s)
35 {
36     std::string t=s;
37     std::transform(s.begin(),s.end(),t.begin(),[](unsigned char c){return std::tolower(c);});
38     if(t=="circle")
39       return GraphType::circle;
40     if (t == "triangle") 
41         return GraphType::triangle;
42     if (t == "rectangle")
43         return GraphType::rectangle;
44     return GraphType::circle;     
45  } 
46 Graph* make_graph(const std::string& type)
47 {
48     switch(str_to_GraphType(type))
49     {
50         case GraphType::circle: 
51            return new Circle;
52         case GraphType::triangle:
53            return new Triangle;
54         case GraphType::rectangle:
55            return new Rectangle;
56         default:return nullptr;          
57     }
58 }

demo3.cpp

 1 #include <string>
 2 #include "Graph.hpp"
 3  void test() {
 4     Canvas canvas;
 5     canvas.add("circle");
 6     canvas.add("triangle");
 7     canvas.add("rectangle");
 8     canvas.paint();
 9  }
10  int main() 
11  {
12     test();
13  }

运行结果截图

实验三

问题1:对象关系识别
(1)写出Graph.hpp中体现"组合"关系的成员声明代码行,并用一句话说明被组合对象的功能。
    答:std::vector<Graph*>graphs;功能存储多个图形对象的指针
(2)写出Graph.hpp中体现"继承"关系的类声明代码行。
    答:class Circle : public Graph
           class Triangle : public Graph
           class Rectangle : public Graph
问题2:多态机制观察
(1)Graph 中的draw 若未声明成虚函数,Canvas::paint() 中g->draw() 运行结果会有何不同?
    答:Canvas::paint() 中g->draw() 调用基类的draw(),而不是实际派生类,会导致结果相同
(2)若Canvas 类std::vector<Graph*> 改成std::vector<Graph> ,会出现什么问题?
    答:破坏多态,draw()调用都输出为空
(3)若~Graph() 未声明成虚函数,会带来什么问题?
      答:删除派生类对象时,调用基类Graph的析构函数,而不会调用派生类的析构函数,~Graph() 未声明成虚函数时无法释放派生类对象
问题3:扩展性思考
若要新增星形Star ,需在哪些文件做哪些改动?逐一列出。
     答:在GraphType枚举中添加新类型star
            添加Star类声明
            添加Star::draw()
            修改str_to_GraphType函数,添加对"star"字符串的处理
            修改make_graph函数,添加star分支
问题4:资源管理
观察make_graph 函数和Canvas 析构函数:
(1)make_graph 返回的对象在什么地方被释放?
    答:Canvas的析构函数中被释放
(2)使用原始指针管理内存有何利弊
    答:利:可以直接控制:可以精确控制内存分配和释放时机
           弊端:容易产生内存泄漏,可能对同一指针多次调用delete

Toy.hpp

 1 #pragma once
 2 #include<string>
 3 #include<vector>
 4 enum class Material {wool,plastic,metal};
 5 class Toy
 6 {
 7     public:
 8         Toy(const std::string& name, Material material, int age);
 9         std::string getName() const ;
10         Material getMaterial() const;
11         int getRecage() const;
12         std::string getMaterialString() const;
13         virtual void display() const;
14         virtual void spefunction()=default; 
15         virtual ~Toy()=default;
16     private:
17        std::string toy_name;
18        Material material;
19        int recage;        
20 };
21 class TeddyBear : public Toy 
22 {
23  public:
24     TeddyBear(const std::string& name);
25     void spefunction() ;
26 };
27 
28 class Car: public Toy 
29 {
30  public:
31     Car(const std::string& name);
32     void spefunction() ;
33 };
34 
35 class Spinner : public Toy 
36 {
37   public:
38     Spinner(const std::string& name);
39     void spefunction() ;
40 };
41 class ToyFactory
42 {
43   public:
44       void add(const std::string& toy_name);
45       void show() const; 
46     ~ToyFactory();
47   private:
48     std::vector<Toy*> toys;     
49 };
50 Toy* make_toy(const std::string& toy_name);

toyfactory.cpp

 1 #include<algorithm>
 2 #include<cctype>
 3 #include<iostream>
 4 #include<string>
 5 #include"toy.hpp"
 6  Toy::Toy(const std::string& name,  Material material, int age): toy_name{name},material{material}, recage{age} {}
 7  std::string Toy:: getName() const 
 8  {
 9      return toy_name;
10 } 
11  Material Toy::getMaterial() const
12  {
13      return material;
14  }
15  std::string Toy::getMaterialString() const
16   {
17     switch(material) {
18         case Material::wool: return "毛绒";
19         case Material::plastic: return "塑料";
20         case Material::metal: return "金属";
21         
22     }
23 }
24   int Toy::getRecage() const
25   {
26       return recage;
27   }
28   void Toy::display() const
29   {
30     std::cout << "玩具名称: " << toy_name << ", 材质:"  << getMaterialString() << ", 推荐年龄: " << recage << "+" << std::endl;
31 }
32   TeddyBear::TeddyBear(const std::string& name) : Toy(name, Material::wool, 3) {}
33   void TeddyBear::spefunction() 
34   {
35     std::cout  << "特异功能: 给予陪伴!\n";
36 }
37   Car::Car(const std::string& name) : Toy(name, Material::plastic, 6) {}
38   void Car::spefunction() 
39   {
40     std::cout  << "特异功能:遥控行驶!\n";
41 }
42   Spinner::Spinner(const std::string& name) : Toy(name, Material::metal,10) {}
43   void Spinner::spefunction() 
44   {
45     std::cout <<  "特异功能:缓解压力!\n";
46 }
47   Toy* make_toy(const std::string& toy_name)
48    {
49        std::string s=toy_name;
50     if(s=="TeddyBear")  
51        return new TeddyBear("泰迪熊");
52     else if(s=="Car")
53        return new Car("小汽车");
54     else if(s=="Spinner")
55        return new Spinner("指尖陀螺");   
56     else 
57     return nullptr;
58  }
59   void ToyFactory::add(const std::string& toy_name)
60   {
61       Toy* t = make_toy(toy_name);
62     if (t) 
63     
64         toys.push_back(t);
65   }
66   void ToyFactory::show() const
67   {
68           for (Toy* t : toys) 
69         {
70         t->display();
71         t->spefunction();
72         std::cout <<std::endl;
73   }
74 }
75   ToyFactory::~ToyFactory()
76   {
77        for (Toy* t: toys) 
78         delete t;
79   }

demo4..cpp

 1 #include<iostream>
 2 #include <string>
 3 #include "Toy.hpp"
 4  void test()
 5  {
 6     ToyFactory toys;
 7     toys.add("TeddyBear");
 8     toys.add("Car");
 9     toys.add("Spinner");
10     toys.show();
11  }
12  int main()
13  {
14      std::cout<<" *************玩具工厂信息*************\n";
15      test(); 
16  }

运行结果截图

实验四

场景描述
   答:记录各种玩具的基本信息(名称、材质、适用年龄)
         展示不同玩具的特有功能
          可以进行添加玩具
陈述各类之间的关系(继承、组合等)及设计理由
    答:继承关系:class TeddyBear : public Toy
                             class Car : public Toy
                              class Spinner : public Toy可以避免重复代码
          组合关系:std::vector<Toy*> toys;
          运用Toy* make_toy(const std::string& toy_name);所有玩具都通过统一接口创建,新 增玩具只需修改工厂函数
           枚举材料 enum class Material {wool, plastic, metal};统一集中材料类型
           纯虚函数:spefunction(),~Toy()在不同派生类中有不同实现

posted @ 2025-12-02 17:16  零和星际  阅读(4)  评论(0)    收藏  举报