实验4 组合与继承
实验任务1
代码
c++
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 };
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 }
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 }
运行测试截图

问题回答
问题1:组合关系识别
GradeCalc 类声明中, 逐行写出所有体现"组合"关系的成员声明,并用一句话说明每个被组合对象的功能。
1、std::string course_name; 组合了一个字符串对象,用于存储课程名称,是课程类的组成部分。
2、std::vector<int> grades; 组合了一个动态整型数组,用于保存录入的所有学生成绩。
3、std::array<int,5>counts; 组合了一个长度为5的整型数组,用于统计各分数段的人数。
4、std::array<double,5> rates; 组合了一个长度为5的双精度浮点数组,用于保存各分数段人数占比。
问题2:接口暴露理解
如在 test 模块中这样使用(GradeCalc c("OOP");c.input(5);c.push_back(97); ),是否合法?如不合法,解释原因。
不合法
因为GradeCalc 没有提供名为 push_back的公共接口。
问题3:架构设计分析
当前设计方案中, compute 在 info 模块中调用:
(1)连续打印3次统计信息, compute 会被调用几次?标记 is_dirty 起到什么作用?
如果这三次打印之间没有修改成绩,则 compute() 只会在第一次info() 时被调用一次。
is_dirty记录成绩数据是否发生变更。只有当数据变更即is_dirty==true时,才需要重新执行统计计算。
(2)如新增 update_grade(index, new_grade) ,这种设计需要更改 compute 调用位置吗?简洁说明理由。
不需要。
只需要在每次更改grade信息后将is_dirty的值置为true,就可以在下次调用info()时更新统计数组。
问题4:功能扩展设计
要增加"中位数"统计, 不新增数据成员怎么做?在哪个函数里加?写出伪代码。
在info()函数中添加。
伪代码(在info()函数中compute();之后添加):
int n = grades.size();
int middle;
if (n % 2 == 1)
middle= grades[n / 2 ];
else
middle= (grades[n / 2-1] + grades[n / 2 ]) * 0.5;
std::cout << "中位数:\t" << middle<< std::endl;
问题5:数据状态管理
GradeCalc 和 compute 中都包含代码: counts.fill(0); rates.fill(0); 。
compute 中能否去掉这两行?如去掉,在哪种使用场景下会引发统计错误?
不能。若去掉,会在多次调用 info()或多次录入会导致累计错误。
问题6:内存管理理解
input 模块中代码 grades.reserve(n); 如果去掉:
(1)对程序功能有影响吗?(去掉重新编译、运行,观察功能是否受影响)
没有
(2)对性能有影响吗?如有影响,用一句话陈述具体影响。
有。
导致多次内存重分配,造成不必要的内存复制开销,降低性能。
实验任务2
代码
c++
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 };
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 }
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 }
运行测试截图

问题回答
继承方式:利用从基类继承的接口,可以直接使用 this 指针进行访问,但容易暴露底层实现细节,破坏封装性。
实验任务3
代码
c++
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); // 创建图形,返回堆对象指针
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 }
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 }
运行测试截图

问题回答
class Circle : public Graph {};
class Triangle : public Graph {};
class Rectangle : public Graph {};
多态失效,容器中存储的全是 Graph 类型对象,不再是原始的 Circle ;
只调用基类中的draw()函数,无法体现具体图形行为。
在 enum class GraphType 中添加新枚举值(Star);
声明新的派生类 Star;
Graph.cpp中:
实现Star::draw();
修改 str_to_GraphType函数,增加对 "Star"的判断;
修改 make_graph 函数,添加 case 分支;
main.cpp中:
在 test() 中添加测试:canvas.add("Star");
实验任务4
代码
c++
1 #ifndef TOY_HPP 2 #define TOY_HPP 3 4 #include <iostream> 5 #include <string> 6 #include <vector> 7 8 using namespace std; 9 10 // 抽象基类:毛绒玩具 11 class Toy { 12 protected: 13 string name; // 玩具名称 14 string type; // 玩具类型 15 string color; // 颜色 16 float price; // 价格 17 string brand; //品牌 18 int energy; //电量 19 public: 20 Toy(string n, string t, string c, float p,string b,int e); 21 virtual ~Toy() = default; // 虚析构函数 22 23 // 特异功能 24 virtual void specialFunction() const = 0; 25 26 // 获取信息(非虚函数) 27 string getName() const ; 28 string getType() const ; 29 string getColor() const; 30 float getPrice() const; 31 string getBrand() const; 32 int getEnergy() const; 33 }; 34 35 // 发声玩具 36 class SoundToy : public Toy { 37 private: 38 string soundContent; // 发声内容 39 public: 40 SoundToy(string n, string t, string c, float p, string b,int e,string sc); 41 // 发声 42 void specialFunction() const override; 43 }; 44 45 //发光玩具 46 class LightToy : public Toy { 47 private: 48 string lightColor; // 发光颜色 49 public: 50 LightToy(string n, string t, string c, float p, string b,int e,string lc); 51 // 发光 52 void specialFunction() const override; 53 }; 54 55 //跳舞玩具 56 class DanceToy : public Toy{ 57 private: 58 string danceType; //舞种 59 public: 60 DanceToy(string n, string t, string c, float p, string b,int e, string dt); 61 // 发光 62 void specialFunction() const override; 63 }; 64 65 //唱歌玩具 66 class SingToy : public Toy{ 67 private: 68 string singSong; //歌名 69 public: 70 SingToy(string n, string t, string c, float p, string b,int e,string ss); 71 72 void specialFunction() const override; 73 }; 74 75 // 玩具工厂类(组合一组Toy对象) 76 class ToyFactory { 77 private: 78 vector<Toy*> toys; // 管理多个Toy指针 79 public: 80 ~ToyFactory(); // 析构 81 82 // 添加玩具 83 void addToy(Toy* t) ; 84 85 // 显示所有玩具信息 86 void displayAllToys() const; 87 //测试特异功能 88 void testAllSpecialFunctions() const; 89 }; 90 91 #endif
1 #include "Toy.hpp" 2 #include<iostream> 3 #include<vector> 4 5 using namespace std; 6 7 //Toy 8 Toy::Toy(string n, string t, string c, float p,string b,int e) : name(n), type(t), color(c), price(p),brand(b),energy(e) {} 9 10 string Toy::getName() const { 11 return name; 12 } 13 string Toy::getType() const { 14 return type; 15 } 16 string Toy::getColor() const { 17 return color; 18 } 19 float Toy::getPrice() const { 20 return price; 21 } 22 string Toy::getBrand() const { 23 return brand; 24 } 25 int Toy::getEnergy() const { 26 return energy; 27 } 28 29 //发声Toy 30 SoundToy::SoundToy(string n, string t, string c, float p, string b,int e,string sc): Toy(n, t, c, p,b,e), soundContent(sc) {} 31 32 void SoundToy::specialFunction() const { 33 cout << "【" << name << "】Say:" << soundContent << endl; 34 35 }; 36 37 //发光Toy 38 LightToy::LightToy(string n, string t, string c, float p, string b,int e,string lc): Toy(n, t, c, p,b,e), lightColor(lc) {} 39 40 void LightToy::specialFunction() const { 41 cout << "【" << name << "】发出" << lightColor << "的光" << endl; 42 } 43 44 //跳舞Toy 45 DanceToy::DanceToy(string n, string t, string c, float p, string b,int e,string dt): Toy(n, t, c, p,b,e), danceType(dt) {} 46 47 void DanceToy::specialFunction() const { 48 cout << "【" << name << "】准备跳" <<danceType<< endl; 49 } 50 51 //唱歌Toy 52 SingToy::SingToy(string n, string t, string c, float p, string b,int e,string ss): Toy(n, t, c, p,b,e), singSong(ss) {} 53 54 void SingToy::specialFunction() const { 55 cout << "【" << name << "】打算唱一首你喜欢的:" <<singSong<< endl; 56 } 57 58 //玩具工厂 59 ToyFactory::~ToyFactory(){ 60 for (auto t : toys) delete t; 61 } 62 63 void ToyFactory::addToy(Toy* t) { 64 toys.push_back(t); 65 } 66 67 void ToyFactory::displayAllToys() const { 68 cout << "===== 玩具工厂清单 =====" << endl; 69 cout<<endl; 70 if(toys.empty()){ 71 cout<<"工厂暂无玩具。\n"; 72 return; 73 } 74 for (const auto t : toys) { 75 cout << "名称:" << t->getName() 76 << " | 类型:" << t->getType() 77 << " | 颜色:" << t->getColor() 78 << " | 价格:" << t->getPrice() << "元" 79 << " | 品牌:" << t->getBrand() 80 << " | 电量:" << t->getEnergy() <<"%"<< endl; 81 cout << "------------------------------------------------------------------------------------" << endl; 82 } 83 } 84 void ToyFactory::testAllSpecialFunctions() const { 85 cout<<"\n=====启动玩具工厂测试系统=====\n"; 86 cout<<endl; 87 if(toys.empty()){ 88 cout<<"工厂暂无可测试玩具。\n"; 89 return; 90 } 91 for (size_t i = 0; i < toys.size(); ++i) { 92 cout << "[" << (i+1) << "] "; 93 toys[i]->specialFunction(); // 多态调用 94 } 95 cout<<endl; 96 cout<<"=====所有玩具测试完毕!=====\n" ; 97 }
1 #include <iostream> 2 #include "Toy.hpp" 3 using namespace std; 4 5 int main() { 6 // 创建工厂 7 ToyFactory factory; 8 // 添加不同类型的玩具 9 factory.addToy(new SoundToy("HeartBear", "毛绒公仔", "棕色", 59.9, 10 "Jelly", 100, "“今天天气很好,有没有见到想见的人^_^”")); 11 factory.addToy(new LightToy("ShiningRabbit", "毛绒挂件", "白色", 29.9, 12 "Stef", 96, "暖黄色")); 13 factory.addToy(new DanceToy("PulsatileKitten", "跳舞玩偶", "灰色", 89.9, 14 "Jelly",43,"爵士舞")); 15 factory.addToy(new SingToy("Puppy", "毛绒公仔", "黄色", 129.9, 16 "Jelly",66,"《这条小鱼在乎》")); 17 18 // 显示所有玩具信息并测试功能 19 factory.displayAllToys(); 20 factory.testAllSpecialFunctions(); 21 22 return 0; 23 }
运行测试截图

问题描述
上述程序设计了一个面向对象的玩具管理系统,模拟一家生产多功能电子毛绒玩具的工厂,实现对不同类型玩具的信息管理与功能测试。
1、定义了抽象的“毛绒玩具”基类,封装通用属性(玩具名称、玩具类型、颜色、价格、品牌和电量);
2、通过继承机制派生出具有不同特异功能的子类(发声玩具、发光玩具、跳舞玩具和唱歌玩具);
3、利用虚函数和多态机制,使用统一接口调用各玩具的特异功能;
4、设计“玩具工厂”类,通过组合方式管理多个玩具对象;
5、实现两个独立功能模块:显示所有玩具的基本信息和测试所有玩具的特异功能;
6、同时便于系统拓展,支持后续添加新类型的玩具。
对象关系
1、SoundToy、LightToy、DanceToy、SingToy均继承自 Toy基类,共享其数据成员和方法,体现“is-a”关系。
2、ToyFactory 类包含一个 vector<Toy*> 成员,用于管理多个玩具指针,体现“has-a”关系。

浙公网安备 33010602011771号