实验四

1.试验任务1

设计性实验任务:用组合实现成绩计算器类。

image

 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);//初始化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);//先分配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>());///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;// std::min_element返回迭代器,需要解引用才是需要的成绩 
 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      
 77     return avg;
 78 }
 79 
 80 void GradeCalc::info() {
 81     if(is_dirty) //更改成绩   
 82        compute();//重新成绩统计 
 83 
 84     std::cout << "课程名称:\t" << course_name << std::endl;
 85     std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() << std::endl;
 86     std::cout << "最高分:\t" << max() << std::endl;
 87     std::cout << "最低分:\t" << min() << std::endl;
 88 
 89     const std::array<std::string, 5> grade_range{"[0, 60) ", 
 90                                            "[60, 70)", 
 91                                            "[70, 80)",
 92                                            "[80, 90)", 
 93                                            "[90, 100]"};
 94     
 95     for(int i = static_cast<int>(grade_range.size())-1; i >= 0; --i)
 96     //atic_cast<int>():将 size_t 类型强制转换为 int(避免无符号整数减 1 时的溢出风险,转换为 int 可正常处理负数)*/
 97         std::cout << grade_range[i] << "\t: " << counts[i] << "人\t"      //输出制表符 \t(对齐格式)
 98                   << std::fixed << std::setprecision(2) << rates[i]*100 << "%\n";
 99 }
100 
101 void GradeCalc::compute() {
102     if(grades.empty())
103         return;
104 
105     counts.fill(0); ///填充初始值 
106     rates.fill(0.0);
107 
108     // 统计各分数段人数
109     for(auto grade:grades) {
110         if(grade < 60)
111             ++counts[0];        // [0, 60)
112         else if (grade < 70)
113             ++counts[1];        // [60, 70)
114         else if (grade < 80)
115             ++counts[2];        // [70, 80)
116         else if (grade < 90)
117             ++counts[3];        // [80, 90)
118         else
119             ++counts[4];        // [90, 100]
120     }
121 
122     // 统计各分数段比例
123     for(size_t i = 0; i < rates.size(); ++i)
124         rates[i] = counts[i] * 1.0 / grades.size();
125     //.size() 的返回值类型就是 size_t用 size_t 作为循环变量以避免类型不匹配”的警告,同时保证下标是非负的。
126     is_dirty = false;  // 更新脏标记
127 }
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 }
demo1.cpp

image

 

问题1:组合关系识别
GradeCalc 类声明中,逐行写出所有体现"组合"关系的成员声明,并用一句话说明每个被组合对象的功能。
1.std::string course_name;组合了string类型,字符串类型存储课程名称
2.std::vector<int> grades;组合了vector类型,动态存储整数型课程成绩,
3.std::array<int, 5> counts;组合了array类型,静态存储整数类型的各个分数段的人数
4.std::array<double, 5> rates;组合了array类型静态存储各个分数段的人数百分比
5.bool is_dirty;组合了bool类型,标记成绩是否有变更。
 
问题2:接口暴露理解
如在 test 模块中这样使用,是否合法?如不合法,解释原因。

image

 不合法,因为push_back 使vector类型中在尾部增加元素的,但是vector类型的grades是私有成员,无法通过外部接口访问

问题3:架构设计分析
当前设计方案中, compute 在 info 模块中调用:
(1)连续打印3次统计信息, compute 会被调用几次?标记 is_dirty 起到什么作用?
0或1次;脏标记,当标记更改时表明成绩更改/更新过,需要重新进行成绩统计。
 
(2)如新增 update_grade(index, new_grade) ,这种设计需要更改 compute 调用位置吗?简洁说明理由。
不需要,因为新增函数只要在函数体内部更改了 is_dirty ,就会在info模块中调用compute,无需重新更改位置
问题4:功能扩展设计
要增加"中位数"统计,不新增数据成员怎么做?在哪个函数里加?写出伪代码。
在类里增加公有成员函数double medium()const ;

image

 

问题5:数据状态管理
GradeCalc 和 compute 中都包含代码: counts.fill(0); rates.fill(0); 。
compute 中能否去掉这两行?如去掉,在哪种使用场景下会引发统计错误?
不可以,在成绩数据变化并且compute被多次调用的时候会引发统计错误
 
问题6:内存管理理解
input 模块中代码 grades.reserve(n); 如果去掉:
(1)对程序功能有影响吗?(去掉重新编译、运行,观察功能是否受影响)
没有
 
(2)对性能有影响吗?如有影响,用一句话陈述具体影响
有,提前分配vector空间,避免多次push_back造成的多次扩容
 
2.试验任务二
设计性实验任务:用继承实现成绩计算器类。
 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 }
demo2.cpp

image

 

问题1:继承关系识别
写出 GradeCalc 类声明体现"继承"关系的完整代码行。
class GradeCalc: private std::vector<int> {
 
问题2:接口暴露理解
当前继承方式下,基类 vector<int> 的接口会自动成为 GradeCalc 的接口吗?
如在 test 模块中这样用,能否编译通过?用一句话解释原因。

image

不会,继承方式时private私有继承,vector内部的的接口不能在外部被调用

问题3:数据访问差异
对比继承方式与组合方式内部实现数据访问的一行典型代码。说明两种方式下的封装差异带来的数据访问接口差异。

image

 1.组合:通过grade直接迭代vector类的grades

2.继承:通过继承了vector接口的*this来迭代

问题4:组合 vs. 继承方案选择
你认为组合方案和继承方案,哪个更适合成绩计算这个问题场景?简洁陈述你的结论和理由
  组合,因为成绩计算类包含存储成绩的成员,是包含has a 的关系,符合组合,而不是范围大小is a 的关系,不大符合传统使用继承的场景
 
3.试验任务三
设计性实验任务:综合运用组合、继承、虚函数实现用一个接口打印不同图形。

image

 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 }
Graph.hpp
 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 }
Graph.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 }
demo3.cpp

image

问题1:对象关系识别
(1)写出Graph.hpp中体现"组合"关系的成员声明代码行,并用一句话说明被组合对象的功能。
std::vector<Graph*> graphs; 通过vector类型的多个graphs成员组合存储了多个Graph*对象。存储所需要绘制的图形们
(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从而空实现
(2)若 Canvas 类 std::vector<Graph*> 改成 std::vector<Graph> ,会出现什么问题?
Graph是抽象类,不可以直接创建对象,会导致编译失败
 
(3)若 ~Graph() 未声明成虚函数,会带来什么问题?
子类Circle Triangle Rectangle无法析构,释放内存,导致内存泄漏
 
问题3:扩展性思考
若要新增星形 Star ,需在哪些文件做哪些改动?逐一列出。
1.hpp文件中
新增枚举类型star  enum class GraphType {circle, triangle, rectangle,star};
新增class Star :public Graph {public :void draw();};
2.cpp文件
新增void Star ::draw(){std::cout<<"draw a star..\n";}
str_to_GraphType中新增if(t =="star")return GraphType::star;
make_graph中switch新增case  GraphType::star:return new Star;
3.demo3.cpp
void test()新增canvas.add(“star”);
 
问题4:资源管理
观察 make_graph 函数和 Canvas 析构函数:
(1) make_graph 返回的对象在什么地方被释放?
在canvas析构函数遍历graphs对每个指针释放内存
(2)使用原始指针管理内存有何利弊?
优点:性能好
缺点:需要手动new delete ,容易导致内存泄露
 
4.试验任务四
应用的问题场景描述:
在本实验中,我们需要设计一个电子毛绒玩具系统。这个系统需要能够创建和管理不同类型的毛绒玩具,并能够通过一个工厂类来生成这些玩具。每个玩具有其特有的功能,例如跳舞、唱歌、发光等。
陈述各类之间的关系(继承、组合等)及设计理由:
组合:毛绒玩具类 Toy和玩具工厂类 ToyFactory     理由:是has a 关系,工厂包含玩具
继承:DanceToy、MusicToy、LightToy继承Toy     理由:是is a 关系,范围大小不同
 
源码
 1 #include<iostream>
 2 #include<vector>
 3 #include<cstring>
 4 
 5 
 6 using namespace std;
 7 
 8 class Toy{
 9     private :
10         string name;
11         string type;
12         double price;
13         int num;
14         
15     public:
16         Toy(string n,string t,double p,int m):name(n),type(t),price(p),num(m){
17         }
18         virtual ~Toy()=default;
19         
20         virtual void basic()const {cout<<"Name:"<<name<<"  Type:"<<type<<"  Price:"
21                                     <<price<<"元  Number:"<<num<<""<<endl;};
22         virtual void special()const =0;
23         
24     
25 };
26 
27 class DanceToy:public Toy{
28     public:
29         DanceToy(string name,double price,int num):Toy(name,"Dance",price,num){
30         }
31         void special()const override{cout<<"Special Function:  Graceful dance."<<endl;
32         }
33 };
34 
35 
36 class MusicToy:public Toy{
37     public:
38         MusicToy(string name,double price,int num):Toy(name,"Music",price,num){
39         }
40         void special()const override{cout<<"Special Function:  Play music."<<endl;
41         }
42 };
43 
44 
45 class LightToy:public Toy{
46     public:
47         LightToy(string name,double price,int num):Toy(name,"Light",price,num){
48         }
49         void special()const override{cout<<"Special Function:  Light up."<<endl;
50         }
51 };
52 
53 class ToyFactory{
54     public:
55         void add(Toy* toy);
56         void info()const;
57         ~ToyFactory();
58         
59     private:
60         vector<Toy*> toys;
61         bool is_dirty=0;
62 };
Toy.hpp
 1 #include<iostream>
 2 #include<vector>
 3 #include<string>
 4 #include <algorithm>
 5 #include <cstdlib>
 6 #include <iomanip>
 7 #include <numeric>
 8 #include "Toy.hpp" 
 9 
10 using namespace std;
11 
12 void ToyFactory::add(Toy* toy)
13 {
14     toys.push_back(toy);
15  } 
16  
17 void ToyFactory::info() const
18 {
19     cout<<"\n工厂内玩具:\n";
20     for(auto* toy:toys){
21         toy->basic();
22         toy->special();
23         cout<<endl;
24     }
25 }
26 
27 ToyFactory::~ToyFactory()
28 {
29     for(auto &toy:toys)delete toy;
30 }
Toy.cpp
 1 #include<iostream>
 2 #include <string>
 3 #include "Toy.hpp"
 4 
 5 int main()
 6 {
 7     ToyFactory f;
 8     f.add(new DanceToy("bear",88,6));
 9     f.add(new MusicToy("bird",66,10));
10     f.add(new LightToy("cat",77,15));
11     f.add(new LightToy("dog",77,19));
12     
13     f.info();
14     return 0;
15 }
demo4.cpp

 

运行测试截图

image

 

 
 
posted @ 2025-12-02 22:09  zxy22213  阅读(0)  评论(0)    收藏  举报